@elytracms/studio 0.0.79 → 0.0.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/convex/contract-parity.test.ts +1 -1
- package/convex/functions.ts +13 -3
- package/convex/http-routes.test.ts +143 -0
- package/convex/http.ts +19 -9
- package/convex/schema.ts +0 -19
- package/convex/users.ts +1 -1
- package/dist/components/app-views/default-modules.d.ts +141 -44
- package/dist/components/app-views/default-modules.js +229 -65
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/app-views/module.d.ts +38 -0
- package/dist/components/app-views/module.js.map +1 -1
- package/dist/components/app-views/sidebar.d.ts +5 -2
- package/dist/components/app-views/sidebar.js +16 -5
- package/dist/components/app-views/sidebar.js.map +1 -1
- package/dist/components/app-views/studio-config.d.ts +27 -0
- package/dist/components/app-views/studio-config.js.map +1 -1
- package/dist/components/builder/canvas-render-context.js +7 -1
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/reuse-context.js +7 -1
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/reuse.d.ts +94 -16
- package/dist/components/builder/reuse.js +201 -19
- package/dist/components/builder/reuse.js.map +1 -1
- package/dist/components/cms/bulk-actions.js +3 -1
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/cell-edit.js +6 -2
- package/dist/components/cms/cell-edit.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +40 -13
- package/dist/components/cms/cms-store.js +50 -22
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +2 -2
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/date-input-core.d.ts +21 -0
- package/dist/components/cms/date-input-core.js +20 -0
- package/dist/components/cms/date-input-core.js.map +1 -1
- package/dist/components/cms/date-input.d.ts +9 -1
- package/dist/components/cms/date-input.js +2 -2
- package/dist/components/cms/date-input.js.map +1 -1
- package/dist/components/cms/detail-editor.js +11 -5
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/editable-cell.js +2 -1
- package/dist/components/cms/editable-cell.js.map +1 -1
- package/dist/components/cms/field-control.js +4 -2
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/field-renderer.js +8 -1
- package/dist/components/cms/field-renderer.js.map +1 -1
- package/dist/components/cms/import-export.js +2 -1
- package/dist/components/cms/import-export.js.map +1 -1
- package/dist/components/cms/issues-pane.d.ts +4 -2
- package/dist/components/cms/issues-pane.js +6 -4
- package/dist/components/cms/issues-pane.js.map +1 -1
- package/dist/components/cms/scalar-value-editor.js +2 -1
- package/dist/components/cms/scalar-value-editor.js.map +1 -1
- package/dist/components/components/transfer-dialog.js +10 -12
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/project-list.d.ts +9 -3
- package/dist/components/project-list.js +11 -5
- package/dist/components/project-list.js.map +1 -1
- package/dist/components/project-switcher.d.ts +3 -1
- package/dist/components/project-switcher.js +4 -2
- package/dist/components/project-switcher.js.map +1 -1
- package/dist/components/redirects/redirects-store.d.ts +21 -0
- package/dist/components/redirects/redirects-store.js +17 -4
- package/dist/components/redirects/redirects-store.js.map +1 -1
- package/dist/components/section-nav.js +102 -4
- package/dist/components/section-nav.js.map +1 -1
- package/dist/customization.d.ts +7 -0
- package/dist/customization.js.map +1 -1
- package/dist/lib/data/collection-content.d.ts +6 -1
- package/dist/lib/data/collection-content.js +50 -12
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +132 -0
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +103 -0
- package/dist/lib/i18n/messages.en.js +146 -0
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/locale/binding-fallback.d.ts +8 -1
- package/dist/lib/locale/binding-fallback.js +8 -1
- package/dist/lib/locale/binding-fallback.js.map +1 -1
- package/dist/lib/locale/locale-store.d.ts +41 -9
- package/dist/lib/locale/locale-store.js +34 -15
- package/dist/lib/locale/locale-store.js.map +1 -1
- package/dist/lib/locale/locale-switcher.d.ts +3 -1
- package/dist/lib/locale/locale-switcher.js +4 -2
- package/dist/lib/locale/locale-switcher.js.map +1 -1
- package/dist/lib/locale/use-studio-locale.d.ts +9 -4
- package/dist/lib/locale/use-studio-locale.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.d.ts +1 -3
- package/dist/lib/persistence/convex-adapter.js +0 -55
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persona/labels.d.ts +42 -1
- package/dist/lib/persona/labels.js +57 -0
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/routes/route-issues.d.ts +9 -0
- package/dist/lib/routes/route-issues.js +117 -0
- package/dist/lib/routes/route-issues.js.map +1 -0
- package/dist/lib/workspace/config-issues.d.ts +54 -0
- package/dist/lib/workspace/config-issues.js +70 -0
- package/dist/lib/workspace/config-issues.js.map +1 -0
- package/dist/lib/workspace/index.d.ts +9 -0
- package/dist/lib/workspace/index.js +11 -0
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/lib/workspace/switcher-source.d.ts +20 -3
- package/dist/lib/workspace/switcher-source.js.map +1 -1
- package/package.json +2 -2
- package/convex/cliTokens.ts +0 -63
- package/convex/sync.ts +0 -297
|
@@ -25,4 +25,11 @@ export interface BoundValueFallback extends DisplayedFallback {
|
|
|
25
25
|
* `null` when the binding is not CMS-shaped, points at no field, or the value
|
|
26
26
|
* is a real translation.
|
|
27
27
|
*/
|
|
28
|
-
export declare function cmsBindingFallback(binding: BindingReference, source: DataSource | undefined,
|
|
28
|
+
export declare function cmsBindingFallback(binding: BindingReference, source: DataSource | undefined,
|
|
29
|
+
/**
|
|
30
|
+
* The locale to judge fallback in. Optional (EC-479): the ambient studio locale
|
|
31
|
+
* is `null` until a project's locale config is installed, and the content engine
|
|
32
|
+
* then resolves in the session's own default — so a caller that has no locale
|
|
33
|
+
* omits it rather than substituting one.
|
|
34
|
+
*/
|
|
35
|
+
locale?: Locale): BoundValueFallback | null;
|
|
@@ -16,7 +16,14 @@ function pathOfToken(token) {
|
|
|
16
16
|
* `null` when the binding is not CMS-shaped, points at no field, or the value
|
|
17
17
|
* is a real translation.
|
|
18
18
|
*/
|
|
19
|
-
export function cmsBindingFallback(binding, source,
|
|
19
|
+
export function cmsBindingFallback(binding, source,
|
|
20
|
+
/**
|
|
21
|
+
* The locale to judge fallback in. Optional (EC-479): the ambient studio locale
|
|
22
|
+
* is `null` until a project's locale config is installed, and the content engine
|
|
23
|
+
* then resolves in the session's own default — so a caller that has no locale
|
|
24
|
+
* omits it rather than substituting one.
|
|
25
|
+
*/
|
|
26
|
+
locale) {
|
|
20
27
|
if (source === undefined || !isCmsSource(source))
|
|
21
28
|
return null;
|
|
22
29
|
if (binding.mode !== 'value' && binding.mode !== 'repeaterItem')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"binding-fallback.js","sourceRoot":"","sources":["../../../src/lib/locale/binding-fallback.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AAEhE,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAsB1F,SAAS,WAAW,CAAC,MAAkB;IACrC,OAAO,MAAM,CAAC,IAAI,KAAK,KAAK,CAAA;AAC9B,CAAC;AAED,4EAA4E;AAC5E,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;SACvC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAA;AAC1E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAyB,EACzB,MAA8B,
|
|
1
|
+
{"version":3,"file":"binding-fallback.js","sourceRoot":"","sources":["../../../src/lib/locale/binding-fallback.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AAEhE,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAsB1F,SAAS,WAAW,CAAC,MAAkB;IACrC,OAAO,MAAM,CAAC,IAAI,KAAK,KAAK,CAAA;AAC9B,CAAC;AAED,4EAA4E;AAC5E,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;SACvC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAA;AAC1E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAyB,EACzB,MAA8B;AAC9B;;;;;GAKG;AACH,MAAe;IAEf,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAA;IAC7D,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,cAAc;QAAE,OAAO,IAAI,CAAA;IAE5E,MAAM,MAAM,GAAG,MAAM,CAAC,MAAwB,CAAA;IAC9C,IAAI,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IACrC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAElC,wEAAwE;IACxE,4EAA4E;IAC5E,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,KAAK,SAAS,CAAA;IAClD,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,UAAU,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAE,CAAA;QACrB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAA;QACpC,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;QACzB,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACtB,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;IACrB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IAEpC,0EAA0E;IAC1E,gDAAgD;IAChD,IAAI,UAAU,GAAG,MAAM,CAAC,UAAU,CAAA;IAClC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,sBAAsB,CAAC,MAAM,CAAC,YAAY,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAA;QAC3F,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAA;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;QACxC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,IAAI,CAAA;QACpC,UAAU,GAAG,KAAK,CAAC,EAAE,CAAA;IACvB,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,GAAG,qBAAqB,CAAC,MAAM,CAAC,YAAY,EAAE,UAAU,EAAE,MAAM,CAAC,CAAA;IAC/E,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAA;IAC9E,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IACvC,OAAO;QACL,KAAK;QACL,YAAY,EAAE,QAAQ,CAAC,YAAY;QACnC,eAAe,EAAE,QAAQ,CAAC,eAAe;KAC1C,CAAA;AACH,CAAC","sourcesContent":["import type { Locale } from '@elytracms/core/cms-core'\nimport type { BindingReference } from '@elytracms/core/project-graph'\nimport type { CmsSource, DataSource } from '@elytracms/core/data-binding'\nimport { listQueryOfConfig } from '../bindings/section-bindings'\nimport type { QueryCmsConfig } from '../bindings/section-bindings'\nimport { getCollectionDocument, runCollectionListQuery } from '../data/collection-content'\nimport type { DisplayedFallback } from './fallback'\n\n/**\n * Fallback visibility for BOUND content (EC-176): given a stored content\n * binding and its CMS source, decide whether the value the canvas currently\n * previews for the active locale is served via EC-018 locale fallback. Used\n * by the inspector's Content tab — the surface that explains what a bound\n * field shows (the canvas overlay itself is out of this wave's reach; see\n * EC-176 notes).\n *\n * Deterministic over the playground delivery engine: the same\n * `@elytracms/core/content` resolution that produced the previewed value reports\n * which fields fell back (`ResolutionInfo.localeFallbacks`), so the label can\n * never disagree with the displayed value.\n */\n\n/** A previewed bound value that fell back, plus which field it came from. */\nexport interface BoundValueFallback extends DisplayedFallback {\n field: string\n}\n\nfunction isCmsSource(source: DataSource): source is CmsSource {\n return source.kind === 'cms'\n}\n\n/** JSON-pointer-ish token → path segments ('/0/title' → ['0', 'title']). */\nfunction pathOfToken(token: string): string[] {\n return token\n .split('/')\n .filter((segment) => segment.length > 0)\n .map((segment) => segment.replaceAll('~1', '/').replaceAll('~0', '~'))\n}\n\n/**\n * Whether the bound field's previewed value (first entry for list/repeater\n * bindings, the bound document otherwise) is a locale fallback in `locale`.\n * `null` when the binding is not CMS-shaped, points at no field, or the value\n * is a real translation.\n */\nexport function cmsBindingFallback(\n binding: BindingReference,\n source: DataSource | undefined,\n /**\n * The locale to judge fallback in. Optional (EC-479): the ambient studio locale\n * is `null` until a project's locale config is installed, and the content engine\n * then resolves in the session's own default — so a caller that has no locale\n * omits it rather than substituting one.\n */\n locale?: Locale,\n): BoundValueFallback | null {\n if (source === undefined || !isCmsSource(source)) return null\n if (binding.mode !== 'value' && binding.mode !== 'repeaterItem') return null\n\n const config = source.config as QueryCmsConfig\n let path = pathOfToken(binding.token)\n if (path.length === 0) return null\n\n // List-shaped value bindings carry a leading entry index; repeater-item\n // bindings preview the first entry (the same convention as ContentPreview).\n let entryIndex = 0\n const listShaped = config.documentId === undefined\n if (binding.mode === 'value' && listShaped) {\n const head = path[0]!\n if (!/^\\d+$/.test(head)) return null\n entryIndex = Number(head)\n path = path.slice(1)\n }\n const field = path[0]\n if (field === undefined) return null\n\n // Resolve the previewed document's id, then ask the delivery engine which\n // fields fell back for it in the active locale.\n let documentId = config.documentId\n if (documentId === undefined) {\n const list = runCollectionListQuery(config.collectionId, listQueryOfConfig(config), locale)\n if (!list.ok) return null\n const entry = list.documents[entryIndex]\n if (entry === undefined) return null\n documentId = entry.id\n }\n\n const { info } = getCollectionDocument(config.collectionId, documentId, locale)\n if (info === null) return null\n const fellBack = info.localeFallbacks.find((entry) => entry.path[0] === field)\n if (fellBack === undefined) return null\n return {\n field,\n sourceLocale: fellBack.sourceLocale,\n requestedLocale: fellBack.requestedLocale,\n }\n}\n"]}
|
|
@@ -11,20 +11,49 @@ import type { Locale, LocaleConfig } from '@elytracms/core/cms-core';
|
|
|
11
11
|
*
|
|
12
12
|
* Deliberately NOT part of `lib/data/data-store` — the locale is studio
|
|
13
13
|
* state, not a data source.
|
|
14
|
+
*
|
|
15
|
+
* EC-479/EC-483 — **before a project's locale config is installed, this store
|
|
16
|
+
* holds `null`, not a locale.** It used to open on the engine's `en/de`
|
|
17
|
+
* `sampleLocaleConfig` fixture, which is the worst fallback in the family: the
|
|
18
|
+
* default locale is the literal STORAGE KEY for every localized field value
|
|
19
|
+
* (`documents.ts:88`), so a fabricated one writes a German project's content
|
|
20
|
+
* under `en` and correcting it later is a data migration, not a config edit. The
|
|
21
|
+
* locale is a deliberate choice at project creation (EC-483) and reaches the
|
|
22
|
+
* studio through {@link configureStudioLocales} from the project's own config —
|
|
23
|
+
* so "not loaded" is an honest state that surfaces, never an invented one that
|
|
24
|
+
* silently becomes a key.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* `{ config, active }` once a project's locales are installed; `{ null, null }`
|
|
28
|
+
* before that. The two fields move together by construction — there is no state
|
|
29
|
+
* with a config but no active locale, or an active locale nobody declared.
|
|
14
30
|
*/
|
|
15
|
-
|
|
31
|
+
export type StudioLocaleState = {
|
|
16
32
|
readonly config: LocaleConfig;
|
|
17
33
|
readonly active: Locale;
|
|
18
|
-
}
|
|
34
|
+
} | {
|
|
35
|
+
readonly config: null;
|
|
36
|
+
readonly active: null;
|
|
37
|
+
};
|
|
19
38
|
/** Snapshot of `{ config, active }` — stable identity until the next change. */
|
|
20
39
|
export declare function getStudioLocaleState(): StudioLocaleState;
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
40
|
+
/**
|
|
41
|
+
* The active studio locale every surface renders in — `null` until a project's
|
|
42
|
+
* locale config is installed. A caller that genuinely needs a locale reads the
|
|
43
|
+
* PROJECT's own default (`CmsStore.localeConfig.default`, the content session's
|
|
44
|
+
* config); it must never substitute one of its own.
|
|
45
|
+
*/
|
|
46
|
+
export declare function getStudioLocale(): Locale | null;
|
|
47
|
+
/**
|
|
48
|
+
* The project locale config driving the switcher options — `null` until one is
|
|
49
|
+
* installed (the studio has no locales of its own to offer).
|
|
50
|
+
*/
|
|
51
|
+
export declare function getStudioLocaleConfig(): LocaleConfig | null;
|
|
25
52
|
/**
|
|
26
53
|
* Switch the ambient locale. Unknown locales are ignored (the switcher only
|
|
27
|
-
* offers configured ones; programmatic callers get a no-op, never a crash)
|
|
54
|
+
* offers configured ones; programmatic callers get a no-op, never a crash), and
|
|
55
|
+
* so is any switch attempted before a config is installed — there is nothing to
|
|
56
|
+
* validate the locale against.
|
|
28
57
|
*/
|
|
29
58
|
export declare function setStudioLocale(locale: Locale): void;
|
|
30
59
|
/**
|
|
@@ -35,6 +64,9 @@ export declare function setStudioLocale(locale: Locale): void;
|
|
|
35
64
|
export declare function configureStudioLocales(config: LocaleConfig): void;
|
|
36
65
|
/** Subscribe to locale/config changes. Returns the unsubscribe function. */
|
|
37
66
|
export declare function subscribeStudioLocale(listener: () => void): () => void;
|
|
38
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* Reset to the pre-config "not loaded" state (tests only — module state is
|
|
69
|
+
* shared). Never re-seeds a fixture config: a test that queries localized content
|
|
70
|
+
* must say which locale it means, exactly like a project does.
|
|
71
|
+
*/
|
|
39
72
|
export declare function resetStudioLocale(): void;
|
|
40
|
-
export {};
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { createLocaleConfig, isKnownLocale
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
};
|
|
1
|
+
import { createLocaleConfig, isKnownLocale } from '@elytracms/core/cms-core';
|
|
2
|
+
/** The pre-config state: no project locales have been installed yet. */
|
|
3
|
+
const NOT_LOADED = { config: null, active: null };
|
|
4
|
+
let state = NOT_LOADED;
|
|
6
5
|
const listeners = new Set();
|
|
7
6
|
function emit() {
|
|
8
7
|
for (const listener of listeners)
|
|
@@ -12,22 +11,34 @@ function emit() {
|
|
|
12
11
|
export function getStudioLocaleState() {
|
|
13
12
|
return state;
|
|
14
13
|
}
|
|
15
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* The active studio locale every surface renders in — `null` until a project's
|
|
16
|
+
* locale config is installed. A caller that genuinely needs a locale reads the
|
|
17
|
+
* PROJECT's own default (`CmsStore.localeConfig.default`, the content session's
|
|
18
|
+
* config); it must never substitute one of its own.
|
|
19
|
+
*/
|
|
16
20
|
export function getStudioLocale() {
|
|
17
21
|
return state.active;
|
|
18
22
|
}
|
|
19
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* The project locale config driving the switcher options — `null` until one is
|
|
25
|
+
* installed (the studio has no locales of its own to offer).
|
|
26
|
+
*/
|
|
20
27
|
export function getStudioLocaleConfig() {
|
|
21
28
|
return state.config;
|
|
22
29
|
}
|
|
23
30
|
/**
|
|
24
31
|
* Switch the ambient locale. Unknown locales are ignored (the switcher only
|
|
25
|
-
* offers configured ones; programmatic callers get a no-op, never a crash)
|
|
32
|
+
* offers configured ones; programmatic callers get a no-op, never a crash), and
|
|
33
|
+
* so is any switch attempted before a config is installed — there is nothing to
|
|
34
|
+
* validate the locale against.
|
|
26
35
|
*/
|
|
27
36
|
export function setStudioLocale(locale) {
|
|
37
|
+
if (state.config === null)
|
|
38
|
+
return;
|
|
28
39
|
if (locale === state.active || !isKnownLocale(state.config, locale))
|
|
29
40
|
return;
|
|
30
|
-
state = {
|
|
41
|
+
state = { config: state.config, active: locale };
|
|
31
42
|
emit();
|
|
32
43
|
}
|
|
33
44
|
/**
|
|
@@ -37,10 +48,14 @@ export function setStudioLocale(locale) {
|
|
|
37
48
|
*/
|
|
38
49
|
export function configureStudioLocales(config) {
|
|
39
50
|
const normalized = createLocaleConfig({ default: config.default, locales: config.locales });
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
normalized.
|
|
43
|
-
|
|
51
|
+
const current = state.config;
|
|
52
|
+
const sameConfig = current !== null &&
|
|
53
|
+
normalized.default === current.default &&
|
|
54
|
+
normalized.locales.length === current.locales.length &&
|
|
55
|
+
normalized.locales.every((locale, i) => locale === current.locales[i]);
|
|
56
|
+
const active = state.active !== null && isKnownLocale(normalized, state.active)
|
|
57
|
+
? state.active
|
|
58
|
+
: normalized.default;
|
|
44
59
|
if (sameConfig && active === state.active)
|
|
45
60
|
return;
|
|
46
61
|
state = { config: normalized, active };
|
|
@@ -53,9 +68,13 @@ export function subscribeStudioLocale(listener) {
|
|
|
53
68
|
listeners.delete(listener);
|
|
54
69
|
};
|
|
55
70
|
}
|
|
56
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Reset to the pre-config "not loaded" state (tests only — module state is
|
|
73
|
+
* shared). Never re-seeds a fixture config: a test that queries localized content
|
|
74
|
+
* must say which locale it means, exactly like a project does.
|
|
75
|
+
*/
|
|
57
76
|
export function resetStudioLocale() {
|
|
58
|
-
state =
|
|
77
|
+
state = NOT_LOADED;
|
|
59
78
|
emit();
|
|
60
79
|
}
|
|
61
80
|
//# sourceMappingURL=locale-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locale-store.js","sourceRoot":"","sources":["../../../src/lib/locale/locale-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"locale-store.js","sourceRoot":"","sources":["../../../src/lib/locale/locale-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAqC5E,wEAAwE;AACxE,MAAM,UAAU,GAAsB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;AAEpE,IAAI,KAAK,GAAsB,UAAU,CAAA;AAEzC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAA;AAEvC,SAAS,IAAI;IACX,KAAK,MAAM,QAAQ,IAAI,SAAS;QAAE,QAAQ,EAAE,CAAA;AAC9C,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,oBAAoB;IAClC,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,CAAC,MAAM,CAAA;AACrB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB;IACnC,OAAO,KAAK,CAAC,MAAM,CAAA;AACrB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI;QAAE,OAAM;IACjC,IAAI,MAAM,KAAK,KAAK,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC;QAAE,OAAM;IAC3E,KAAK,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAA;IAChD,IAAI,EAAE,CAAA;AACR,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAoB;IACzD,MAAM,UAAU,GAAG,kBAAkB,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;IAC3F,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAA;IAC5B,MAAM,UAAU,GACd,OAAO,KAAK,IAAI;QAChB,UAAU,CAAC,OAAO,KAAK,OAAO,CAAC,OAAO;QACtC,UAAU,CAAC,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,MAAM;QACpD,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IACxE,MAAM,MAAM,GACV,KAAK,CAAC,MAAM,KAAK,IAAI,IAAI,aAAa,CAAC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC;QAC9D,CAAC,CAAC,KAAK,CAAC,MAAM;QACd,CAAC,CAAC,UAAU,CAAC,OAAO,CAAA;IACxB,IAAI,UAAU,IAAI,MAAM,KAAK,KAAK,CAAC,MAAM;QAAE,OAAM;IACjD,KAAK,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,CAAA;IACtC,IAAI,EAAE,CAAA;AACR,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,qBAAqB,CAAC,QAAoB;IACxD,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IACvB,OAAO,GAAG,EAAE;QACV,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAC5B,CAAC,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB;IAC/B,KAAK,GAAG,UAAU,CAAA;IAClB,IAAI,EAAE,CAAA;AACR,CAAC","sourcesContent":["import { createLocaleConfig, isKnownLocale } from '@elytracms/core/cms-core'\nimport type { Locale, LocaleConfig } from '@elytracms/core/cms-core'\n\n/**\n * The studio's ambient locale (EC-176) — framework-free core.\n *\n * One studio-level active locale, set once via the locale switcher, that the\n * builder canvas preview, the CMS table, and the detail editor all follow.\n * Module-level (not React context) on purpose: the playground's content\n * client (`lib/data/collection-content`) reads it at query time without any\n * provider plumbing, and React surfaces subscribe through\n * `useStudioLocale` (`useSyncExternalStore`).\n *\n * Deliberately NOT part of `lib/data/data-store` — the locale is studio\n * state, not a data source.\n *\n * EC-479/EC-483 — **before a project's locale config is installed, this store\n * holds `null`, not a locale.** It used to open on the engine's `en/de`\n * `sampleLocaleConfig` fixture, which is the worst fallback in the family: the\n * default locale is the literal STORAGE KEY for every localized field value\n * (`documents.ts:88`), so a fabricated one writes a German project's content\n * under `en` and correcting it later is a data migration, not a config edit. The\n * locale is a deliberate choice at project creation (EC-483) and reaches the\n * studio through {@link configureStudioLocales} from the project's own config —\n * so \"not loaded\" is an honest state that surfaces, never an invented one that\n * silently becomes a key.\n */\n\n/**\n * `{ config, active }` once a project's locales are installed; `{ null, null }`\n * before that. The two fields move together by construction — there is no state\n * with a config but no active locale, or an active locale nobody declared.\n */\nexport type StudioLocaleState =\n | { readonly config: LocaleConfig; readonly active: Locale }\n | { readonly config: null; readonly active: null }\n\n/** The pre-config state: no project locales have been installed yet. */\nconst NOT_LOADED: StudioLocaleState = { config: null, active: null }\n\nlet state: StudioLocaleState = NOT_LOADED\n\nconst listeners = new Set<() => void>()\n\nfunction emit(): void {\n for (const listener of listeners) listener()\n}\n\n/** Snapshot of `{ config, active }` — stable identity until the next change. */\nexport function getStudioLocaleState(): StudioLocaleState {\n return state\n}\n\n/**\n * The active studio locale every surface renders in — `null` until a project's\n * locale config is installed. A caller that genuinely needs a locale reads the\n * PROJECT's own default (`CmsStore.localeConfig.default`, the content session's\n * config); it must never substitute one of its own.\n */\nexport function getStudioLocale(): Locale | null {\n return state.active\n}\n\n/**\n * The project locale config driving the switcher options — `null` until one is\n * installed (the studio has no locales of its own to offer).\n */\nexport function getStudioLocaleConfig(): LocaleConfig | null {\n return state.config\n}\n\n/**\n * Switch the ambient locale. Unknown locales are ignored (the switcher only\n * offers configured ones; programmatic callers get a no-op, never a crash), and\n * so is any switch attempted before a config is installed — there is nothing to\n * validate the locale against.\n */\nexport function setStudioLocale(locale: Locale): void {\n if (state.config === null) return\n if (locale === state.active || !isKnownLocale(state.config, locale)) return\n state = { config: state.config, active: locale }\n emit()\n}\n\n/**\n * Install the project's locale config (e.g. when a CMS session or builder\n * shell mounts with a starter kit's locales). The active locale is kept when\n * it is still configured, otherwise it snaps back to the new default.\n */\nexport function configureStudioLocales(config: LocaleConfig): void {\n const normalized = createLocaleConfig({ default: config.default, locales: config.locales })\n const current = state.config\n const sameConfig =\n current !== null &&\n normalized.default === current.default &&\n normalized.locales.length === current.locales.length &&\n normalized.locales.every((locale, i) => locale === current.locales[i])\n const active =\n state.active !== null && isKnownLocale(normalized, state.active)\n ? state.active\n : normalized.default\n if (sameConfig && active === state.active) return\n state = { config: normalized, active }\n emit()\n}\n\n/** Subscribe to locale/config changes. Returns the unsubscribe function. */\nexport function subscribeStudioLocale(listener: () => void): () => void {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n}\n\n/**\n * Reset to the pre-config \"not loaded\" state (tests only — module state is\n * shared). Never re-seeds a fixture config: a test that queries localized content\n * must say which locale it means, exactly like a project does.\n */\nexport function resetStudioLocale(): void {\n state = NOT_LOADED\n emit()\n}\n"]}
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* builder toolbar and the CMS surface header, both driving the same
|
|
4
4
|
* studio-level active locale. Options come from the project locale config;
|
|
5
5
|
* the default locale is marked. Renders nothing for single-locale projects —
|
|
6
|
-
* no per-control locale ceremony anywhere else
|
|
6
|
+
* no per-control locale ceremony anywhere else — nor before a project's locale
|
|
7
|
+
* config has been installed (EC-479: there is nothing to switch between, and the
|
|
8
|
+
* engine no longer supplies its own `en/de` fixture to pretend otherwise).
|
|
7
9
|
*/
|
|
8
10
|
export declare function LocaleSwitcher({ className }: {
|
|
9
11
|
className?: string;
|
|
@@ -8,11 +8,13 @@ import { useStudioLocale } from './use-studio-locale.js';
|
|
|
8
8
|
* builder toolbar and the CMS surface header, both driving the same
|
|
9
9
|
* studio-level active locale. Options come from the project locale config;
|
|
10
10
|
* the default locale is marked. Renders nothing for single-locale projects —
|
|
11
|
-
* no per-control locale ceremony anywhere else
|
|
11
|
+
* no per-control locale ceremony anywhere else — nor before a project's locale
|
|
12
|
+
* config has been installed (EC-479: there is nothing to switch between, and the
|
|
13
|
+
* engine no longer supplies its own `en/de` fixture to pretend otherwise).
|
|
12
14
|
*/
|
|
13
15
|
export function LocaleSwitcher({ className }) {
|
|
14
16
|
const { locale, config, setLocale } = useStudioLocale();
|
|
15
|
-
if (config.locales.length <= 1)
|
|
17
|
+
if (config === null || config.locales.length <= 1)
|
|
16
18
|
return null;
|
|
17
19
|
return (_jsxs("div", { role: "group", "aria-label": "Active locale", "data-testid": "locale-switcher", className: cn('flex items-center gap-1 rounded-md bg-muted/50 p-0.5', className), children: [_jsx(Globe, { "aria-hidden": true, className: "ml-1 size-3.5 self-center text-muted-foreground" }), config.locales.map((option) => {
|
|
18
20
|
const isDefault = option === config.default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locale-switcher.js","sourceRoot":"","sources":["../../../src/lib/locale/locale-switcher.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,EAAE,EAAE,EAAE,MAAM,UAAU,CAAA;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAErD
|
|
1
|
+
{"version":3,"file":"locale-switcher.js","sourceRoot":"","sources":["../../../src/lib/locale/locale-switcher.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,EAAE,EAAE,EAAE,MAAM,UAAU,CAAA;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAErD;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,EAAE,SAAS,EAA0B;IAClE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,eAAe,EAAE,CAAA;IACvD,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAE9D,OAAO,CACL,eACE,IAAI,EAAC,OAAO,gBACD,eAAe,iBACd,iBAAiB,EAC7B,SAAS,EAAE,EAAE,CAAC,sDAAsD,EAAE,SAAS,CAAC,aAEhF,KAAC,KAAK,yBAAa,SAAS,EAAC,iDAAiD,GAAG,EAChF,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;gBAC7B,MAAM,SAAS,GAAG,MAAM,KAAK,MAAM,CAAC,OAAO,CAAA;gBAC3C,OAAO,CACL,kBAEE,IAAI,EAAC,QAAQ,kBACC,MAAM,KAAK,MAAM,EAC/B,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,iBAC7E,iBAAiB,MAAM,EAAE,EACtC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,EAChC,SAAS,EAAE,EAAE,CACX,2EAA2E,EAC3E,MAAM,KAAK,MAAM;wBACf,CAAC,CAAC,yCAAyC;wBAC3C,CAAC,CAAC,6CAA6C,CAClD,aAEA,MAAM,EACN,SAAS,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,iCAAiC,uBAAS,CAAC,CAAC,CAAC,IAAI,KAdzE,MAAM,CAeJ,CACV,CAAA;YACH,CAAC,CAAC,IACE,CACP,CAAA;AACH,CAAC","sourcesContent":["import { Globe } from 'lucide-react'\nimport { cn } from '../utils'\nimport { localeChipLabel } from './fallback'\nimport { useStudioLocale } from './use-studio-locale'\n\n/**\n * The ambient locale switcher (EC-176): one segmented control, mounted in the\n * builder toolbar and the CMS surface header, both driving the same\n * studio-level active locale. Options come from the project locale config;\n * the default locale is marked. Renders nothing for single-locale projects —\n * no per-control locale ceremony anywhere else — nor before a project's locale\n * config has been installed (EC-479: there is nothing to switch between, and the\n * engine no longer supplies its own `en/de` fixture to pretend otherwise).\n */\nexport function LocaleSwitcher({ className }: { className?: string }) {\n const { locale, config, setLocale } = useStudioLocale()\n if (config === null || config.locales.length <= 1) return null\n\n return (\n <div\n role=\"group\"\n aria-label=\"Active locale\"\n data-testid=\"locale-switcher\"\n className={cn('flex items-center gap-1 rounded-md bg-muted/50 p-0.5', className)}\n >\n <Globe aria-hidden className=\"ml-1 size-3.5 self-center text-muted-foreground\" />\n {config.locales.map((option) => {\n const isDefault = option === config.default\n return (\n <button\n key={option}\n type=\"button\"\n aria-pressed={option === locale}\n title={isDefault ? `${localeChipLabel(option)} (default locale)` : localeChipLabel(option)}\n data-testid={`locale-option-${option}`}\n onClick={() => setLocale(option)}\n className={cn(\n 'rounded-[5px] px-2 py-0.5 text-xs font-medium uppercase transition-colors',\n option === locale\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground',\n )}\n >\n {option}\n {isDefault ? <span className=\"ml-0.5 text-muted-foreground/70\">·</span> : null}\n </button>\n )\n })}\n </div>\n )\n}\n"]}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { Locale, LocaleConfig } from '@elytracms/core/cms-core';
|
|
2
2
|
export interface StudioLocale {
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
/**
|
|
4
|
+
* The ambient active locale every studio surface renders in — `null` before a
|
|
5
|
+
* project's locale config is installed (EC-479: the engine no longer opens on
|
|
6
|
+
* its own `en/de` fixture). A surface that must render in *some* locale reads
|
|
7
|
+
* the project's own default; it never invents one.
|
|
8
|
+
*/
|
|
9
|
+
locale: Locale | null;
|
|
10
|
+
/** The project locale config driving the switcher options — `null` until installed. */
|
|
11
|
+
config: LocaleConfig | null;
|
|
7
12
|
setLocale: (locale: Locale) => void;
|
|
8
13
|
}
|
|
9
14
|
/** Subscribe a React surface to the studio's ambient locale (EC-176). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-studio-locale.js","sourceRoot":"","sources":["../../../src/lib/locale/use-studio-locale.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;AAE5C,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,qBAAqB,GACtB,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"use-studio-locale.js","sourceRoot":"","sources":["../../../src/lib/locale/use-studio-locale.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;AAE5C,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,qBAAqB,GACtB,MAAM,gBAAgB,CAAA;AAevB,yEAAyE;AACzE,MAAM,UAAU,eAAe;IAC7B,MAAM,KAAK,GAAG,oBAAoB,CAChC,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,CACrB,CAAA;IACD,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,CAAA;AACnF,CAAC","sourcesContent":["import { useSyncExternalStore } from 'react'\nimport type { Locale, LocaleConfig } from '@elytracms/core/cms-core'\nimport {\n getStudioLocaleState,\n setStudioLocale,\n subscribeStudioLocale,\n} from './locale-store'\n\nexport interface StudioLocale {\n /**\n * The ambient active locale every studio surface renders in — `null` before a\n * project's locale config is installed (EC-479: the engine no longer opens on\n * its own `en/de` fixture). A surface that must render in *some* locale reads\n * the project's own default; it never invents one.\n */\n locale: Locale | null\n /** The project locale config driving the switcher options — `null` until installed. */\n config: LocaleConfig | null\n setLocale: (locale: Locale) => void\n}\n\n/** Subscribe a React surface to the studio's ambient locale (EC-176). */\nexport function useStudioLocale(): StudioLocale {\n const state = useSyncExternalStore(\n subscribeStudioLocale,\n getStudioLocaleState,\n getStudioLocaleState,\n )\n return { locale: state.active, config: state.config, setLocale: setStudioLocale }\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ConvexHttpClient } from 'convex/browser';
|
|
2
2
|
import type { CollectionDef } from '@elytracms/core/cms-core';
|
|
3
|
-
import type { AssetRepository, BackendStatus, BackendValidationInput, BackendValidationResult, BlobStorageAdapter,
|
|
3
|
+
import type { AssetRepository, BackendStatus, BackendValidationInput, BackendValidationResult, BlobStorageAdapter, CmsRepository, DeliveryRepository, MembersRepository, PersistenceAdapter, ReferenceIndexRepository, RedirectRepository, UserRepository, InviteRepository } from '@elytracms/core/persistence';
|
|
4
4
|
/**
|
|
5
5
|
* Convex-backed {@link PersistenceAdapter} (brief §10, EC-039). Every repository
|
|
6
6
|
* is implemented against the Convex functions in `apps/builder/convex/*`, mapping
|
|
@@ -19,8 +19,6 @@ export declare class ConvexPersistenceAdapter implements PersistenceAdapter {
|
|
|
19
19
|
readonly users: UserRepository;
|
|
20
20
|
/** Editorial redirects rail — data-backed 301/302 forwards (EC-303). */
|
|
21
21
|
readonly redirects: RedirectRepository;
|
|
22
|
-
/** Revocable CLI sync tokens, stored hashed (EC-150). */
|
|
23
|
-
readonly cliTokens: CliTokenRepository;
|
|
24
22
|
/** Convex file storage behind the EC-133 blob boundary (EC-151). */
|
|
25
23
|
readonly blobs: BlobStorageAdapter;
|
|
26
24
|
/** Delivery-pipeline (instant-publish) health, read-only (EC-268/EC-329). */
|
|
@@ -612,58 +612,6 @@ class ConvexInviteRepository {
|
|
|
612
612
|
}
|
|
613
613
|
}
|
|
614
614
|
}
|
|
615
|
-
function cliTokenRecordFromDoc(doc) {
|
|
616
|
-
return {
|
|
617
|
-
id: doc.tokenId,
|
|
618
|
-
label: doc.label,
|
|
619
|
-
tokenHash: doc.tokenHash,
|
|
620
|
-
createdAt: doc.createdAt,
|
|
621
|
-
...(doc.revokedAt ? { revokedAt: doc.revokedAt } : {}),
|
|
622
|
-
};
|
|
623
|
-
}
|
|
624
|
-
class ConvexCliTokenRepository {
|
|
625
|
-
client;
|
|
626
|
-
clock;
|
|
627
|
-
constructor(client, clock = realClock) {
|
|
628
|
-
this.client = client;
|
|
629
|
-
this.clock = clock;
|
|
630
|
-
}
|
|
631
|
-
async createToken(input) {
|
|
632
|
-
const doc = (await this.client.mutation(mutationRef('cliTokens:create'), {
|
|
633
|
-
tokenId: newId('tok'),
|
|
634
|
-
label: input.label,
|
|
635
|
-
tokenHash: input.tokenHash,
|
|
636
|
-
createdAt: this.clock(),
|
|
637
|
-
}));
|
|
638
|
-
return cliTokenRecordFromDoc(doc);
|
|
639
|
-
}
|
|
640
|
-
async listTokens() {
|
|
641
|
-
const docs = (await this.client.query(queryRef('cliTokens:list'), {}));
|
|
642
|
-
return docs.map(cliTokenRecordFromDoc);
|
|
643
|
-
}
|
|
644
|
-
async revokeToken(tokenId) {
|
|
645
|
-
try {
|
|
646
|
-
const doc = (await this.client.mutation(mutationRef('cliTokens:revoke'), {
|
|
647
|
-
tokenId,
|
|
648
|
-
revokedAt: this.clock(),
|
|
649
|
-
}));
|
|
650
|
-
return cliTokenRecordFromDoc(doc);
|
|
651
|
-
}
|
|
652
|
-
catch (error) {
|
|
653
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
654
|
-
if (/not-found/i.test(message)) {
|
|
655
|
-
throw new RecordNotFoundError('cli-token', tokenId);
|
|
656
|
-
}
|
|
657
|
-
throw error;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
async findByHash(tokenHash) {
|
|
661
|
-
const doc = (await this.client.query(queryRef('cliTokens:byHash'), {
|
|
662
|
-
tokenHash,
|
|
663
|
-
}));
|
|
664
|
-
return doc ? cliTokenRecordFromDoc(doc) : undefined;
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
615
|
function redirectEntryFromDoc(doc, projectId) {
|
|
668
616
|
return {
|
|
669
617
|
id: doc.redirectId,
|
|
@@ -764,8 +712,6 @@ export class ConvexPersistenceAdapter {
|
|
|
764
712
|
users;
|
|
765
713
|
/** Editorial redirects rail — data-backed 301/302 forwards (EC-303). */
|
|
766
714
|
redirects;
|
|
767
|
-
/** Revocable CLI sync tokens, stored hashed (EC-150). */
|
|
768
|
-
cliTokens;
|
|
769
715
|
/** Convex file storage behind the EC-133 blob boundary (EC-151). */
|
|
770
716
|
blobs;
|
|
771
717
|
/** Delivery-pipeline (instant-publish) health, read-only (EC-268/EC-329). */
|
|
@@ -794,7 +740,6 @@ export class ConvexPersistenceAdapter {
|
|
|
794
740
|
this.users = new ConvexUserRepository(client);
|
|
795
741
|
this.invites = new ConvexInviteRepository(client);
|
|
796
742
|
this.redirects = new ConvexRedirectRepository(client);
|
|
797
|
-
this.cliTokens = new ConvexCliTokenRepository(client);
|
|
798
743
|
this.blobs = new ConvexBlobStorage(client);
|
|
799
744
|
this.delivery = new ConvexDeliveryRepository(client);
|
|
800
745
|
}
|