@elytracms/studio 0.0.98 → 0.0.100

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.
Files changed (95) hide show
  1. package/convex/cms.ts +29 -2
  2. package/convex/ingest.test.ts +84 -7
  3. package/convex/ingest.ts +110 -31
  4. package/convex/path-segment-refusal.test.ts +201 -0
  5. package/convex/records-column-transform.convex.test.ts +149 -0
  6. package/convex/records-one-write.convex.test.ts +218 -0
  7. package/convex/schema.ts +1 -1
  8. package/convex/version-kind-stamping.test.ts +118 -0
  9. package/dist/addon.d.ts +5 -3
  10. package/dist/addon.js +4 -3
  11. package/dist/addon.js.map +1 -1
  12. package/dist/components/app-views/default-modules.d.ts +10 -3
  13. package/dist/components/app-views/default-modules.js +5 -1
  14. package/dist/components/app-views/default-modules.js.map +1 -1
  15. package/dist/components/app-views/module.d.ts +15 -8
  16. package/dist/components/app-views/module.js.map +1 -1
  17. package/dist/components/builder/reuse-context.d.ts +29 -1
  18. package/dist/components/builder/reuse-context.js +58 -12
  19. package/dist/components/builder/reuse-context.js.map +1 -1
  20. package/dist/components/cms/cms-store.d.ts +3 -2
  21. package/dist/components/cms/cms-store.js +8 -2
  22. package/dist/components/cms/cms-store.js.map +1 -1
  23. package/dist/components/cms/field-control.js +15 -5
  24. package/dist/components/cms/field-control.js.map +1 -1
  25. package/dist/components/cms/use-document-draft.d.ts +4 -1
  26. package/dist/components/cms/use-document-draft.js +4 -1
  27. package/dist/components/cms/use-document-draft.js.map +1 -1
  28. package/dist/components/cms/use-publish-controller.d.ts +7 -0
  29. package/dist/components/cms/use-publish-controller.js +17 -3
  30. package/dist/components/cms/use-publish-controller.js.map +1 -1
  31. package/dist/components/cms/versions-pane.js +8 -5
  32. package/dist/components/cms/versions-pane.js.map +1 -1
  33. package/dist/components/delivery-health-banner.d.ts +34 -16
  34. package/dist/components/delivery-health-banner.js +59 -27
  35. package/dist/components/delivery-health-banner.js.map +1 -1
  36. package/dist/components/issue-severity.d.ts +3 -2
  37. package/dist/components/issue-severity.js.map +1 -1
  38. package/dist/components/nav-issues-registry.d.ts +39 -24
  39. package/dist/components/nav-issues-registry.js +50 -21
  40. package/dist/components/nav-issues-registry.js.map +1 -1
  41. package/dist/components/nav-issues.d.ts +23 -50
  42. package/dist/components/nav-issues.js +31 -59
  43. package/dist/components/nav-issues.js.map +1 -1
  44. package/dist/components/notifications/notice-center.d.ts +61 -0
  45. package/dist/components/notifications/notice-center.js +119 -0
  46. package/dist/components/notifications/notice-center.js.map +1 -0
  47. package/dist/components/notifications/notice-model.d.ts +137 -0
  48. package/dist/components/notifications/notice-model.js +107 -0
  49. package/dist/components/notifications/notice-model.js.map +1 -0
  50. package/dist/components/notifications/outcome-notice.d.ts +16 -0
  51. package/dist/components/notifications/outcome-notice.js +34 -0
  52. package/dist/components/notifications/outcome-notice.js.map +1 -0
  53. package/dist/components/notifications/shell-findings.d.ts +36 -0
  54. package/dist/components/notifications/shell-findings.js +140 -0
  55. package/dist/components/notifications/shell-findings.js.map +1 -0
  56. package/dist/components/rail-view-source.js +5 -0
  57. package/dist/components/rail-view-source.js.map +1 -1
  58. package/dist/components/records/record-rail.d.ts +10 -2
  59. package/dist/components/records/record-rail.js +85 -4
  60. package/dist/components/records/record-rail.js.map +1 -1
  61. package/dist/components/section-nav.d.ts +15 -3
  62. package/dist/components/section-nav.js +39 -147
  63. package/dist/components/section-nav.js.map +1 -1
  64. package/dist/components/studio-project-screen.js.map +1 -1
  65. package/dist/components/studio-shell.js +8 -3
  66. package/dist/components/studio-shell.js.map +1 -1
  67. package/dist/components/use-narrow-viewport.d.ts +11 -0
  68. package/dist/components/use-narrow-viewport.js +25 -0
  69. package/dist/components/use-narrow-viewport.js.map +1 -0
  70. package/dist/components/users/role-policy-drift-issues.d.ts +14 -7
  71. package/dist/components/users/role-policy-drift-issues.js +17 -8
  72. package/dist/components/users/role-policy-drift-issues.js.map +1 -1
  73. package/dist/lib/collection-view/collection-view-source.d.ts +4 -2
  74. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  75. package/dist/lib/engine-version.d.ts +1 -1
  76. package/dist/lib/engine-version.js +1 -1
  77. package/dist/lib/engine-version.js.map +1 -1
  78. package/dist/lib/i18n/messages.de.js +22 -3
  79. package/dist/lib/i18n/messages.de.js.map +1 -1
  80. package/dist/lib/i18n/messages.en.d.ts +20 -2
  81. package/dist/lib/i18n/messages.en.js +25 -4
  82. package/dist/lib/i18n/messages.en.js.map +1 -1
  83. package/dist/lib/notices/toast-store.d.ts +61 -0
  84. package/dist/lib/notices/toast-store.js +116 -0
  85. package/dist/lib/notices/toast-store.js.map +1 -0
  86. package/dist/lib/operations/cms-persistence.d.ts +24 -4
  87. package/dist/lib/operations/cms-persistence.js +411 -80
  88. package/dist/lib/operations/cms-persistence.js.map +1 -1
  89. package/dist/lib/persistence/convex-adapter.d.ts +2 -0
  90. package/dist/lib/persistence/convex-adapter.js +127 -31
  91. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  92. package/dist/ui/toast.d.ts +24 -16
  93. package/dist/ui/toast.js +23 -32
  94. package/dist/ui/toast.js.map +1 -1
  95. package/package.json +3 -3
@@ -1,6 +1,6 @@
1
- import { ConvexError } from 'convex/values';
1
+ import { ConvexError, convexToJson } from 'convex/values';
2
2
  import { DRAFT_VARIANT, documentKey, documentSchema } from '@elytracms/core/cms-core';
3
- import { PRESENCE_TTL_MS, RecordNotFoundError, PersistenceValidationError, RevisionPreconditionError, StrictReferencePreconditionError, presenceInputSchema, assetCreateDefaults, assetRecordFromRow, compareReferenceEntries, persistenceIssueSchema, presentWriteKeys, recordEntryFromWire, validateForPersistence, versionNotPinnableError, } from '@elytracms/core/persistence';
3
+ import { BackendUnavailableError, PRESENCE_TTL_MS, RecordNotFoundError, PersistenceValidationError, RevisionPreconditionError, StrictReferencePreconditionError, presenceInputSchema, assetCreateDefaults, assetRecordFromRow, compareReferenceEntries, isPersistenceError, persistenceIssueSchema, presentWriteKeys, recordEntryFromWire, rejectedWriteMessage, validateForPersistence, versionNotPinnableError, } from '@elytracms/core/persistence';
4
4
  import { normalizeInviteEmail } from '@elytracms/core/persistence';
5
5
  import { createUuidIdFactory } from '@elytracms/core/determinism';
6
6
  import { SUBSCRIPTION_UNAUTHENTICATED_MESSAGE, queryRef, mutationRef } from './convex-client.js';
@@ -17,6 +17,104 @@ function newId(prefix) {
17
17
  mintUuid ??= createUuidIdFactory();
18
18
  return `${prefix}_${mintUuid()}`;
19
19
  }
20
+ /**
21
+ * EC-686 — what a Convex call's promise rejects with, classified by WHO failed (the binding
22
+ * rule, wave-0100). `convex/browser` (1.42.1, `http_client.js`) rejects with a raw
23
+ * `TypeError` for a fetch rejection, with `Error(<body text>)` for any non-OK status (the
24
+ * status discarded), and with the function's `Error(message)` or `ConvexError` for a
25
+ * function failure: none of them says whether the store JUDGED the write. So a validation
26
+ * verdict the engine throws on purpose and this adapter maps ({@link asRailWriteError}) is
27
+ * that error, and anything else is a {@link BackendUnavailableError} carrying the original
28
+ * message (an expired session is read from it — `isSessionExpiredError`): the session
29
+ * keeps the write and re-sends it (`keepsFailedWrite`, EC-674). An `unknown` is then only
30
+ * ever a failure in THIS process, which the same input repeats.
31
+ */
32
+ function backendFailure(error) {
33
+ const mapped = asRailWriteError(error);
34
+ if (isPersistenceError(mapped))
35
+ return mapped;
36
+ return new BackendUnavailableError(error instanceof Error ? error.message : String(error), { cause: error });
37
+ }
38
+ /**
39
+ * EC-686 — the client's calls with their promise classified ({@link backendFailure}), the
40
+ * one seam every repository calls through: never a whole adapter method, so a crash of
41
+ * this process before or after the call (a parse, a row mapping) stays `unknown`. The
42
+ * arguments are serialised for the wire here first (`convexToJson`, the client's own
43
+ * conversion): a value Convex cannot carry (a `$`-prefixed or non-ASCII key) fails in this
44
+ * process, never inside the call — it is the input, not the backend. The shared client
45
+ * itself is never patched (`lib/auth/**` reads its errors as they are).
46
+ */
47
+ function judgedCalls(client) {
48
+ const judged = (args, call) => {
49
+ convexToJson(args);
50
+ return call().catch((error) => {
51
+ throw backendFailure(error);
52
+ });
53
+ };
54
+ return {
55
+ query: ((ref, args = {}) => judged(args, () => client.query(ref, args))),
56
+ mutation: ((ref, args = {}) => judged(args, () => client.mutation(ref, args))),
57
+ };
58
+ }
59
+ /** Issue code: a record write holding a value the Convex wire cannot carry (EC-702). */
60
+ export const UNCARRIABLE_VALUE = 'uncarriable-value';
61
+ /** Why Convex cannot carry `value` (its own conversion's reason, without the echoed object), or `undefined`. */
62
+ function wireRefusal(value) {
63
+ try {
64
+ convexToJson(value);
65
+ return undefined;
66
+ }
67
+ catch (error) {
68
+ const message = error instanceof Error ? error.message : String(error);
69
+ return message.split(' (present at path')[0].split(' To learn about')[0].trim();
70
+ }
71
+ }
72
+ /**
73
+ * EC-702 — the located refusals of a record write's values the Convex wire cannot carry: a
74
+ * `Date`, a `Map`, a class instance, an `undefined` in a list, a `$`-prefixed or non-ASCII key.
75
+ * The judge is Convex's own conversion (`convexToJson`, the one {@link judgedCalls} runs on every
76
+ * call's arguments), run per column so the refusal names the column — and, below it, the deepest
77
+ * value or key that fails. The in-memory store refuses the same write located, by the column's
78
+ * own schema; without this the Convex path threw an unlocated `Error` in this process
79
+ * (`unknown`). Before EC-702 the operations' step 1 located it, by running the column schemas a
80
+ * second time.
81
+ */
82
+ function uncarriableIssues(write) {
83
+ const issues = [];
84
+ for (const [column, value] of Object.entries(write)) {
85
+ const found = uncarriableAt(value, [column]);
86
+ if (found) {
87
+ issues.push({
88
+ code: UNCARRIABLE_VALUE,
89
+ message: `The Convex store cannot carry this value: ${found.reason}`,
90
+ path: found.path,
91
+ domain: 'persistence',
92
+ });
93
+ }
94
+ }
95
+ return issues;
96
+ }
97
+ function uncarriableAt(value, path) {
98
+ const reason = wireRefusal(value);
99
+ if (reason === undefined)
100
+ return undefined;
101
+ const plain = typeof value === 'object' &&
102
+ value !== null &&
103
+ (Array.isArray(value) || Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
104
+ if (plain) {
105
+ for (const [key, item] of Object.entries(value)) {
106
+ const segment = Array.isArray(value) ? Number(key) : key;
107
+ // A key Convex refuses is the refusal itself, wherever its value is fine.
108
+ const keyReason = Array.isArray(value) ? undefined : wireRefusal({ [key]: null });
109
+ if (keyReason !== undefined)
110
+ return { path: [...path, segment], reason: keyReason };
111
+ const inner = uncarriableAt(item, [...path, segment]);
112
+ if (inner)
113
+ return inner;
114
+ }
115
+ }
116
+ return { path, reason };
117
+ }
20
118
  /**
21
119
  * Row → contract record. `projectId` is the caller's (the row has none): before
22
120
  * EC-577 this copied the row's nonexistent `projectId`, so every connected
@@ -411,11 +509,12 @@ class ConvexCmsRepository {
411
509
  }
412
510
  }
413
511
  /**
414
- * A rail write the handler rejected (EC-577): `resolveRecordWrite` found the write
415
- * invalid inside the transaction and threw a `validation` `ConvexError` — mapped
416
- * back onto the provider-neutral {@link PersistenceValidationError} the in-memory
417
- * adapter throws for the same write. `ConvexError` data survives a production
418
- * deployment's error redaction, unlike a plain thrown message.
512
+ * A write the handler rejected (EC-577): `resolveRecordWrite` (a rail or record write) or
513
+ * the asset upsert found the write invalid inside the transaction and threw a `validation`
514
+ * `ConvexError` — mapped back onto the provider-neutral {@link PersistenceValidationError}
515
+ * the in-memory adapter throws for the same write. `ConvexError` data survives a production
516
+ * deployment's error redaction, unlike a plain thrown message. EC-686: read by
517
+ * {@link backendFailure} for every call, so no call site maps it itself.
419
518
  */
420
519
  function asRailWriteError(error) {
421
520
  if (!(error instanceof ConvexError))
@@ -440,16 +539,12 @@ class ConvexAssetRepository {
440
539
  // EC-577: send the write as written — absent keys stay absent (keep), `null`
441
540
  // crosses the wire as the clear. The handler resolves it against the stored
442
541
  // row inside the transaction; the create defaults ride along for a create.
443
- const doc = (await this.client
444
- .mutation(mutationRef('assets:upsert'), {
542
+ const doc = (await this.client.mutation(mutationRef('assets:upsert'), {
445
543
  assetId: id,
446
544
  ...presentWriteKeys(write),
447
545
  defaults: assetCreateDefaults(projectId, id),
448
546
  createdAt: now,
449
547
  updatedAt: now,
450
- })
451
- .catch((error) => {
452
- throw asRailWriteError(error);
453
548
  }));
454
549
  return assetRecordFromRow(doc, projectId);
455
550
  }
@@ -826,11 +921,7 @@ class ConvexRecordRepository {
826
921
  return `${this.kind.module}:${fn}`;
827
922
  }
828
923
  async list(projectId, query = {}) {
829
- const rows = (await this.client
830
- .query(queryRef(this.ref('list')), presentWriteKeys({ ...query }))
831
- .catch((error) => {
832
- throw asRailWriteError(error);
833
- }));
924
+ const rows = (await this.client.query(queryRef(this.ref('list')), presentWriteKeys({ ...query })));
834
925
  return rows.map((row) => recordEntryFromWire(this.kind, row, projectId));
835
926
  }
836
927
  async get(projectId, id) {
@@ -849,15 +940,17 @@ class ConvexRecordRepository {
849
940
  const now = this.clock();
850
941
  const { id: givenId, ...write } = input;
851
942
  const id = givenId ?? newId(this.kind.idPrefix);
852
- const row = (await this.client
853
- .mutation(mutationRef(this.ref('save')), {
943
+ const sent = presentWriteKeys(write);
944
+ // EC-702: a value the wire cannot carry is the write's located refusal, as in memory.
945
+ const uncarriable = uncarriableIssues(sent);
946
+ if (uncarriable.length > 0) {
947
+ throw new PersistenceValidationError(rejectedWriteMessage(this.kind.kind, id, uncarriable), uncarriable);
948
+ }
949
+ const row = (await this.client.mutation(mutationRef(this.ref('save')), {
854
950
  [this.kind.idKey]: id,
855
- ...presentWriteKeys(write),
951
+ ...sent,
856
952
  createdAt: now,
857
953
  updatedAt: now,
858
- })
859
- .catch((error) => {
860
- throw asRailWriteError(error);
861
954
  }));
862
955
  return recordEntryFromWire(this.kind, row, projectId);
863
956
  }
@@ -931,23 +1024,26 @@ export class ConvexPersistenceAdapter {
931
1024
  */
932
1025
  constructor(client, projectId, options = {}) {
933
1026
  this.client = client;
934
- this.cms = new ConvexCmsRepository(client, options.reactive);
935
- this.assets = new ConvexAssetRepository(client);
936
- this.references = new ConvexReferenceIndexRepository(client);
937
- this.members = new ConvexMembersRepository(client, projectId);
938
- this.users = new ConvexUserRepository(client);
939
- this.invites = new ConvexInviteRepository(client);
1027
+ // EC-686: every repository calls through the classified calls; the probe below and the
1028
+ // blob boundary (its own failure handling) keep the client as it is.
1029
+ const calls = judgedCalls(client);
1030
+ this.cms = new ConvexCmsRepository(calls, options.reactive);
1031
+ this.assets = new ConvexAssetRepository(calls);
1032
+ this.references = new ConvexReferenceIndexRepository(calls);
1033
+ this.members = new ConvexMembersRepository(calls, projectId);
1034
+ this.users = new ConvexUserRepository(calls);
1035
+ this.invites = new ConvexInviteRepository(calls);
940
1036
  const recordRepositories = new Map();
941
1037
  this.records = (kind) => {
942
1038
  let repository = recordRepositories.get(kind);
943
1039
  if (!repository) {
944
- repository = new ConvexRecordRepository(client, kind);
1040
+ repository = new ConvexRecordRepository(calls, kind);
945
1041
  recordRepositories.set(kind, repository);
946
1042
  }
947
1043
  return repository;
948
1044
  };
949
1045
  this.blobs = new ConvexBlobStorage(client);
950
- this.delivery = new ConvexDeliveryRepository(client);
1046
+ this.delivery = new ConvexDeliveryRepository(calls);
951
1047
  }
952
1048
  async status() {
953
1049
  try {