Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .changeset/25921-providers-upload-fold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@object-ui/fields': patch
'@object-ui/data-objectstack': patch
---

refactor(fields, data-objectstack)!: `UploadProvider` moves into `@object-ui/fields`, the ObjectStack upload adapter into `@object-ui/data-objectstack`, and `@object-ui/providers` is retired

Clause-②: yes (narrowing) — `@object-ui/fields` gains `UploadProvider`, `useUpload`, `UploadProviderProps` and the three upload contract types, and `@object-ui/data-objectstack` gains `createObjectStackUploadAdapter` and `ObjectStackUploadAdapterOptions`; the whole `@object-ui/providers` package is retired, which takes `createS3Adapter`, `createAzureBlobAdapter`, their option types and the exported `createObjectUrlAdapter` with it.
Metadata-protocol: none

<!-- adr-0087: not-required (runtime-export-retired @object-ui/providers#*) the retired operand is a published ui package whose entry held a React upload context, its hook, four upload adapter factories and their TypeScript option and result types; no packages/spec schema, registered node type, ADR-0087 conversion surface or stored shape names any of them, so there is nothing for `objectstack migrate meta` to rewrite, and a consumer meets the move at its own import, where the compiler names the missing module -->

**BREAKING.** `@object-ui/providers` is retired: `18.3.0` is its last published version, and it gets no further release. Its one remaining job was the upload context the file, image and avatar widgets of `@object-ui/fields` upload through, so that context now lives in `@object-ui/fields` itself, and the adapter that talks to an ObjectStack server's storage service lives in `@object-ui/data-objectstack`, beside the other fetch logic against that server. The upload contract the two share is `@object-ui/types`'.

What leaves, and what to do:

- FROM `@object-ui/providers` TO `@object-ui/fields` and `@object-ui/data-objectstack`: import `UploadProvider`, `useUpload` and the `UploadProviderProps`, `UploadAdapter`, `UploadOptions` and `UploadResult` types from `@object-ui/fields` (the three contract types also from `@object-ui/types`), and `createObjectStackUploadAdapter` with `ObjectStackUploadAdapterOptions` from `@object-ui/data-objectstack`, then drop the `@object-ui/providers` dependency. `createS3Adapter`, `createAzureBlobAdapter`, `S3AdapterOptions` and `AzureBlobAdapterOptions` had no consumer and leave with no replacement: a host that uploads to its own bucket writes an object with a `name` and an `upload(file, options)` method against the `UploadAdapter` type of `@object-ui/types` and hands it to `UploadProvider`. `createObjectUrlAdapter` is no longer exported: it stays the local `blob:` adapter `useUpload` falls back to when no `UploadProvider` is mounted.

What does not change: `UploadProvider`, `useUpload` and `createObjectStackUploadAdapter` keep their signatures and behaviour, the presigned upload request is the same, and the widgets read the same context.
10 changes: 10 additions & 0 deletions .changeset/25921-upload-adapter-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@object-ui/types': patch
---

feat(types): `@object-ui/types` publishes the upload adapter contract, `UploadAdapter`, `UploadOptions` and `UploadResult`

Clause-②: yes (widening)
Metadata-protocol: none

The three interfaces the file, image and avatar widgets upload through, unchanged in shape from the `@object-ui/providers` package they leave. They live here, as `DataSource` does, because `@object-ui/fields` (whose `UploadProvider` reads an adapter) and `@object-ui/data-objectstack` (whose `createObjectStackUploadAdapter` builds one) do not depend on each other. A host that writes its own adapter types it from here. Nothing existing changes.
1 change: 0 additions & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@
"@object-ui/plugin-timeline",
"@object-ui/plugin-tree",
"@object-ui/plugin-view",
"@object-ui/providers",
"@object-ui/react",
"@object-ui/react-runtime",
"@object-ui/types"
Expand Down
1 change: 0 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,6 @@ Atoms and fields stay light; heavy widgets live in lazy-loaded plugins.
| Package | Description |
| :--- | :--- |
| [`@object-ui/app-shell`](ui/packages/app-shell) | The Console's shell, views and Studio; a private workspace package, last published to npm as 18.2.0 |
| [`@object-ui/providers`](ui/packages/providers) | The upload adapter context the file and image fields upload through |
| [`@object-ui/auth`](ui/packages/auth) | `AuthProvider`, `useAuth`, `AuthGuard`, sign-in forms |
| [`@object-ui/permissions`](ui/packages/permissions) | Object / field / row-level permission guards and hooks |
| [`@object-ui/i18n`](ui/packages/i18n) | Language packs, RTL layout, date and currency formatting |
Expand Down
34 changes: 0 additions & 34 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion ui/apps/console/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@
"@object-ui/plugin-timeline": "workspace:*",
"@object-ui/plugin-tree": "workspace:*",
"@object-ui/plugin-view": "workspace:*",
"@object-ui/providers": "workspace:*",
"@object-ui/react": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@object-ui/types": "workspace:*",
Expand Down
3 changes: 2 additions & 1 deletion ui/apps/console/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import { lazy, Suspense, useCallback, useEffect, useMemo } from 'react';
import { BrowserRouter, Routes, Route, Navigate, Outlet, matchPath, useLocation, useNavigate } from 'react-router-dom';
import { useNativeHostRouting } from '@object-ui/mobile';
import { AuthProvider, useAuth, createAuthenticatedFetch } from '@object-ui/auth';
import { UploadProvider, createObjectStackUploadAdapter } from '@object-ui/providers';
import { UploadProvider } from '@object-ui/fields';
import { createObjectStackUploadAdapter } from '@object-ui/data-objectstack';
import { DevMasterDetail } from './dev/DevMasterDetail';
import { DevLists } from './dev/DevLists';
import { DevModal } from './dev/DevModal';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ const { adapterOptions, uploadSpy, passthrough, stub, MINTED_FILE_ID } = vi.hois
return { adapterOptions, uploadSpy, passthrough, stub, MINTED_FILE_ID };
});

// The console's OWN adapter factory, replaced by a spy that mints a `sys_file`
// id. `UploadProvider` / `useUpload` stay real — the context lookup is the
// thing under test.
vi.mock('@object-ui/providers', async (importOriginal) => ({
// The console's OWN adapter factory (`@object-ui/data-objectstack`), replaced by
// a spy that mints a `sys_file` id. `UploadProvider` / `useUpload`
// (`@object-ui/fields`) stay real — the context lookup is the thing under test.
vi.mock('@object-ui/data-objectstack', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
createObjectStackUploadAdapter: (opts: Record<string, unknown>) => {
adapterOptions.push(opts);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, waitFor, cleanup } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { UploadProvider } from '@object-ui/providers';
import { UploadProvider } from '@object-ui/fields';
import { createI18n } from '@object-ui/i18n';
import { FormPage } from './FormPage';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { render, screen, waitFor, cleanup } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { createI18n } from '@object-ui/i18n';
import { UploadProvider } from '@object-ui/providers';
import { UploadProvider } from '@object-ui/fields';
import { FormPage } from './FormPage';

const { toastError } = vi.hoisted(() => ({ toastError: vi.fn() }));
Expand Down
2 changes: 1 addition & 1 deletion ui/apps/console/src/components/FormPage.fileField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, waitFor, cleanup } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { UploadProvider } from '@object-ui/providers';
import { UploadProvider } from '@object-ui/fields';
import { createI18n } from '@object-ui/i18n';
import { FormPage } from './FormPage';

Expand Down
2 changes: 1 addition & 1 deletion ui/apps/console/src/pages/system/ProfilePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
SelectTrigger,
SelectValue,
} from '@object-ui/components';
import { useUpload } from '@object-ui/providers';
import { useUpload } from '@object-ui/fields';
import { ConsolePageHeader } from '@object-ui/app-shell';
import { useDisplayLocale, useObjectTranslation, type TranslateFn } from '@object-ui/i18n';
import { useAdapter, extractFieldErrors, extractWriteErrorMessage } from '@object-ui/react';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const { translate, findOne } = vi.hoisted(() => ({
findOne: vi.fn(async () => ({ id: 'usr_admin' })),
}));

vi.mock('@object-ui/providers', async (importOriginal) => ({
vi.mock('@object-ui/fields', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
useUpload: () => ({ upload: async () => ({ url: '' }) }),
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ vi.mock('@object-ui/auth', async (importOriginal) => ({
}),
}));

vi.mock('@object-ui/providers', async (importOriginal) => ({
vi.mock('@object-ui/fields', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
useUpload: () => ({ upload: async () => ({ url: '' }) }),
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ vi.mock('@object-ui/auth', async (importOriginal) => ({
}),
}));

vi.mock('@object-ui/providers', async (importOriginal) => ({
vi.mock('@object-ui/fields', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
useUpload: () => ({ upload: async () => ({ url: '' }) }),
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ vi.mock('@object-ui/auth', async (importOriginal) => ({
}),
}));

vi.mock('@object-ui/providers', async (importOriginal) => ({
vi.mock('@object-ui/fields', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
useUpload: () => ({ upload: async () => ({ url: '' }) }),
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ vi.mock('@object-ui/auth', async (importOriginal) => ({
}),
}));

vi.mock('@object-ui/providers', async (importOriginal) => ({
vi.mock('@object-ui/fields', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
useUpload: () => ({ upload: async () => ({ url: '' }) }),
}));
Expand Down
1 change: 0 additions & 1 deletion ui/apps/console/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,6 @@ const workspaceAliases: Record<string, string> = {
'@object-ui/data-objectstack': path.resolve(import.meta.dirname, '../../packages/data-objectstack/src'),
'@object-ui/auth': path.resolve(import.meta.dirname, '../../packages/auth/src'),
'@object-ui/permissions': path.resolve(import.meta.dirname, '../../packages/permissions/src'),
'@object-ui/providers': path.resolve(import.meta.dirname, '../../packages/providers/src'),
'@object-ui/collaboration': path.resolve(import.meta.dirname, '../../packages/collaboration/src'),
'@object-ui/i18n': path.resolve(import.meta.dirname, '../../packages/i18n/src'),
'@object-ui/mobile': path.resolve(import.meta.dirname, '../../packages/mobile/src'),
Expand Down
1 change: 0 additions & 1 deletion ui/packages/app-shell/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@
"@object-ui/mobile": "workspace:*",
"@object-ui/permissions": "workspace:*",
"@object-ui/plugin-editor": "workspace:*",
"@object-ui/providers": "workspace:*",
"@object-ui/react": "workspace:*",
"@object-ui/types": "workspace:*",
"@objectstack/formula": "workspace:^",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ vi.mock('@object-ui/i18n', async (importActual) => ({
}),
}));

vi.mock('@object-ui/providers', async (importActual) => ({
...(await importActual<typeof import('@object-ui/providers')>()),
vi.mock('@object-ui/fields', async (importActual) => ({
...(await importActual<typeof import('@object-ui/fields')>()),
useUpload: () => ({ upload: vi.fn() }),
}));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
import { useAuth } from '@object-ui/auth';
import type { AuthOrganizationMember } from '@object-ui/auth';
import { useObjectTranslation } from '@object-ui/i18n';
import { useUpload } from '@object-ui/providers';
import { useUpload } from '@object-ui/fields';
import { Loader2, Upload, X } from 'lucide-react';
import { toast } from 'sonner';
import { useNavigate } from 'react-router-dom';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/
import { describe, it, expect, vi, afterEach } from 'vitest';
import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react';
import { UploadProvider, type UploadResult } from '@object-ui/providers';
import { UploadProvider, type UploadResult } from '@object-ui/fields';
import type { ActionParamDef } from '@object-ui/core';
import { ActionParamDialog } from './ActionParamDialog';

Expand Down
4 changes: 2 additions & 2 deletions ui/packages/app-shell/src/views/RecordAttachmentsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
Ban,
RefreshCw,
} from 'lucide-react';
import { createObjectStackUploadAdapter } from '@object-ui/providers';
import { createObjectStackUploadAdapter } from '@object-ui/data-objectstack';
import { createAuthenticatedFetch } from '@object-ui/auth';
import { useObjectTranslation, isPermissionError, classifyLoadError } from '@object-ui/react';
import { usePermissions } from '@object-ui/permissions';
Expand All @@ -45,7 +45,7 @@ import { RECORD_PAGE_ATTACHMENT_ROWS, recordAttachmentsCountField } from './reco
* and the enforcement seam always agree).
*
* Storage model: one `sys_file` row per uploaded blob (three-step
* presigned upload via @object-ui/providers' ObjectStack adapter), one
* presigned upload via @object-ui/data-objectstack's ObjectStack adapter), one
* `sys_attachment` join row linking it to `(parent_object, parent_id)`.
* Downloads fetch a short-lived signed URL from `/storage/files/:fileId/url`
* with the console's Bearer token (the endpoint requires an authenticated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/re
// Module scope, per AGENTS.md 测试纪律: the file leg reaches `FileField`
// through a `React.lazy` factory inside `@object-ui/fields`.
import '@object-ui/fields';
import { UploadProvider, type UploadResult } from '@object-ui/providers';
import { UploadProvider, type UploadResult } from '@object-ui/fields';
import { FlowRunner, type ScreenFlowState } from '../FlowRunner';
import { screenResumeValues } from '../ScreenView';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '
/** The fake presigned adapter: `upload` resolving means a `sys_file` was committed. */
const adapterUpload = vi.fn();

vi.mock('@object-ui/providers', async (importOriginal) => ({
vi.mock('@object-ui/data-objectstack', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
createObjectStackUploadAdapter: () => ({ upload: adapterUpload }),
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* `RecordChatterPanel` (plugin-detail/renderers/record-chatter.tsx).
*
* Registered here (app-shell) rather than in plugin-detail because the panel
* depends on `@object-ui/providers` (presigned upload adapter) and
* depends on `@object-ui/data-objectstack` (presigned upload adapter) and
* `@object-ui/auth` (Bearer fetch), which plugin-detail deliberately does not
* pull in. The side-effect registration is imported from the app-shell barrel
* (`src/index.ts`), so any host that mounts the shell — the console runtime
Expand Down
8 changes: 5 additions & 3 deletions ui/packages/cli/src/__tests__/workspace-vite.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,18 +143,20 @@ describe('workspace alias table (objectui#3890)', () => {
}
});

it('aliases the five the dependency scan could not reach past them', () => {
it('aliases the four the dependency scan could not reach past them', () => {
// A Vite dependency scan reports only the first layer it fails on, so the
// card's list of four was the visible half. These sat behind them in the
// same import graph and would have stayed broken under a backfill. The
// sixth, `@object-ui/sdui-parser`, was retired for `@objectstack/sdui-parser`
// at the repo root (objectstack#25712 item 3), which resolves through its
// built `dist` like `@objectstack/spec` and is outside this table.
// built `dist` like `@objectstack/spec` and is outside this table. The
// fifth, `@object-ui/providers`, was retired into `@object-ui/fields` and
// `@object-ui/data-objectstack` (objectstack#25921), so no package is left
// for it to alias.
for (const name of [
'@object-ui/data-objectstack',
'@object-ui/i18n',
'@object-ui/plugin-map',
'@object-ui/providers',
'@object-ui/react-runtime'
]) {
expect(aliases[name]).toBeDefined();
Expand Down
13 changes: 13 additions & 0 deletions ui/packages/data-objectstack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,19 @@ its own row. Only rows whose value is a JSON object are returned. A failed
read loads as `{}`, and a failed write is reported to `onError` and
swallowed. A key outside the family is refused and nothing is written.

## File Uploads

`createObjectStackUploadAdapter` is the browser-side client for the server's
storage service (`/api/v1/storage` unless `basePath` says otherwise). It
requests a presigned upload, PUTs the bytes, and completes the upload, which
registers the `sys_file` row; it answers the stable `/files/FILE_ID` download
URL, with the `sys_file` id in `meta.fileId`. Hand it to `UploadProvider` from
`@object-ui/fields`, whose README shows the wiring. Its `scope` option is the
storage scope every upload from that adapter is filed under, typed by the
spec's upload-scope vocabulary (`API.UploadScope` from `@object-ui/types`;
`attachments` is the scope of files joined to records). Leave it out and the
request carries no scope, so the server applies its default.

## API Reference

### ObjectStackAdapter
Expand Down
6 changes: 6 additions & 0 deletions ui/packages/data-objectstack/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7998,3 +7998,9 @@ export type {
MetadataAuditEntry,
MetadataAuditResponse,
} from './metadata-client';
// The browser-side upload adapter for an ObjectStack server's storage service:
// fetch logic, so it lives in this package. It implements `@object-ui/types`'s
// `UploadAdapter`, which `UploadProvider` from `@object-ui/fields` makes
// ambient for the file, image and avatar widgets.
export { createObjectStackUploadAdapter } from './upload-adapter';
export type { ObjectStackUploadAdapterOptions } from './upload-adapter';
Loading
Loading