@cosmicdrift/kumiko-framework 0.269.2 → 0.270.0
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/README.md +5 -1
- package/package.json +3 -3
- package/src/__tests__/anonymous-access.integration.test.ts +1 -1
- package/src/__tests__/error-contract.integration.test.ts +1 -1
- package/src/__tests__/fixtures/migrate-open-to-all/manual-only.expected.ts +16 -0
- package/src/__tests__/fixtures/migrate-open-to-all/manual-only.input.ts +16 -0
- package/src/__tests__/fixtures/migrate-open-to-all/test-nested-shorthand.expected.ts +12 -0
- package/src/__tests__/fixtures/migrate-open-to-all/test-nested-shorthand.input.ts +12 -0
- package/src/__tests__/fixtures/migrate-open-to-all/test-simple.expected.ts +7 -0
- package/src/__tests__/fixtures/migrate-open-to-all/test-simple.input.ts +7 -0
- package/src/__tests__/full-stack.integration.test.ts +2 -2
- package/src/__tests__/migrate-open-to-all-codemod.test.ts +84 -0
- package/src/__tests__/transition-guard.integration.test.ts +7 -7
- package/src/api/__tests__/api.test.ts +2 -2
- package/src/api/__tests__/body-limit.test.ts +1 -1
- package/src/api/__tests__/pii-leak-guard.integration.test.ts +2 -2
- package/src/api/__tests__/request-locale.integration.test.ts +1 -1
- package/src/api/__tests__/server-error-logging.test.ts +1 -1
- package/src/changes.json +8 -0
- package/src/db/__tests__/list-filter-field-access.integration.test.ts +1 -1
- package/src/derivatives/__tests__/derivatives-context.integration.test.ts +2 -2
- package/src/engine/__tests__/boot-validator-dashboard.test.ts +5 -5
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +1 -1
- package/src/engine/__tests__/boot-validator-projection-list.test.ts +17 -17
- package/src/engine/__tests__/boot-validator-query-output-schema.test.ts +18 -18
- package/src/engine/__tests__/boot-validator-query-refs.test.ts +3 -3
- package/src/engine/__tests__/boot-validator-s0-integration.test.ts +2 -2
- package/src/engine/__tests__/boot-validator.test.ts +18 -18
- package/src/engine/__tests__/build-app-schema.test.ts +10 -6
- package/src/engine/__tests__/build-config-feature-schema-secrets.test.ts +48 -0
- package/src/engine/__tests__/build-config-feature-schema.test.ts +34 -5
- package/src/engine/__tests__/engine.test.ts +39 -25
- package/src/engine/__tests__/entity-handlers.test.ts +19 -8
- package/src/engine/__tests__/entity-presave-wiring.integration.test.ts +2 -2
- package/src/engine/__tests__/extends-registrar.test.ts +3 -3
- package/src/engine/__tests__/feature-crud-shorthand.test.ts +9 -3
- package/src/engine/__tests__/feature-crud-verb-access.integration.test.ts +1 -1
- package/src/engine/__tests__/hook-phases.test.ts +5 -5
- package/src/engine/__tests__/lifecycle-hooks.test.ts +9 -3
- package/src/engine/__tests__/multiselect-filter.integration.test.ts +1 -1
- package/src/engine/__tests__/post-query-hook.test.ts +6 -2
- package/src/engine/__tests__/projection-detail-actions.test.ts +2 -2
- package/src/engine/__tests__/projection-detail-singleton.test.ts +3 -3
- package/src/engine/__tests__/projection-detail-tabs.test.ts +3 -3
- package/src/engine/__tests__/projection-list-filter.integration.test.ts +1 -1
- package/src/engine/__tests__/registry.test.ts +19 -13
- package/src/engine/__tests__/screen.test.ts +26 -20
- package/src/engine/boot-validator/__tests__/access-declarations.test.ts +5 -4
- package/src/engine/boot-validator/__tests__/parent-ref.test.ts +18 -6
- package/src/engine/boot-validator/access-declarations.ts +3 -5
- package/src/engine/boot-validator/nav.ts +4 -3
- package/src/engine/build-config-feature-schema.ts +9 -2
- package/src/engine/entity-handlers.ts +1 -1
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +3 -1
- package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +2 -2
- package/src/engine/feature-ast/__tests__/parse.test.ts +106 -4
- package/src/engine/feature-ast/__tests__/patcher.test.ts +3 -3
- package/src/engine/feature-ast/__tests__/read-optional-access-rule.test.ts +4 -4
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +5 -1
- package/src/engine/feature-ast/extractors/handlers.ts +16 -2
- package/src/engine/feature-ast/extractors/hooks.ts +25 -4
- package/src/engine/feature-ast/patcher.ts +15 -3
- package/src/engine/feature-ast/patterns.ts +4 -0
- package/src/engine/feature-ast/render.ts +3 -0
- package/src/engine/pattern-library/shared-fields.ts +7 -3
- package/src/entrypoint/__tests__/entrypoint-attach-dispatcher.integration.test.ts +1 -1
- package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +3 -3
- package/src/entrypoint/__tests__/split-deploy.integration.test.ts +1 -1
- package/src/es-ops/__tests__/runner.integration.test.ts +2 -2
- package/src/jobs/__tests__/job-event-trigger.integration.test.ts +2 -2
- package/src/jobs/__tests__/job-multi-trigger.integration.test.ts +2 -2
- package/src/jobs/__tests__/jobs.integration.test.ts +1 -1
- package/src/observability/__tests__/observability.integration.test.ts +7 -7
- package/src/pipeline/__tests__/archive-stream.integration.test.ts +2 -2
- package/src/pipeline/__tests__/dispatcher.test.ts +15 -15
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +10 -10
- package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +1 -1
- package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
- package/src/pipeline/__tests__/query-projection.integration.test.ts +3 -3
- package/src/scripts/codemod/migrate-open-to-all.ts +248 -0
- package/src/secrets/__tests__/contains-secret.test.ts +5 -5
- package/src/stack/__tests__/request-helper.integration.test.ts +3 -3
- package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +1 -1
- package/src/testing/__tests__/e2e-generator.test.ts +19 -5
|
@@ -87,7 +87,7 @@ describe("defineFeature", () => {
|
|
|
87
87
|
const _email: string = event.payload.email;
|
|
88
88
|
return { isSuccess: true, data: { id: 1 } };
|
|
89
89
|
},
|
|
90
|
-
{ access: { openToAll:
|
|
90
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
91
91
|
);
|
|
92
92
|
});
|
|
93
93
|
|
|
@@ -105,7 +105,7 @@ describe("defineFeature", () => {
|
|
|
105
105
|
isSuccess: true,
|
|
106
106
|
data: null,
|
|
107
107
|
}),
|
|
108
|
-
{ access: { openToAll:
|
|
108
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
109
109
|
);
|
|
110
110
|
});
|
|
111
111
|
expect(ref?.name).toBe("order:create");
|
|
@@ -115,7 +115,7 @@ describe("defineFeature", () => {
|
|
|
115
115
|
let ref: { name: string } | undefined;
|
|
116
116
|
defineFeature("test", (r) => {
|
|
117
117
|
ref = r.queryHandler("order:list", z.object({}), async () => [], {
|
|
118
|
-
access: { openToAll:
|
|
118
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
119
119
|
});
|
|
120
120
|
});
|
|
121
121
|
expect(ref?.name).toBe("order:list");
|
|
@@ -130,13 +130,15 @@ describe("defineFeature", () => {
|
|
|
130
130
|
async function* (query) {
|
|
131
131
|
yield `echo:${query.payload.prompt}`;
|
|
132
132
|
},
|
|
133
|
-
{ access: { openToAll:
|
|
133
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
134
134
|
);
|
|
135
135
|
});
|
|
136
136
|
expect(ref?.name).toBe("chat:complete");
|
|
137
137
|
|
|
138
138
|
const stored = feature.streamHandlers["chat:complete"];
|
|
139
|
-
expect(stored?.access).toEqual({
|
|
139
|
+
expect(stored?.access).toEqual({
|
|
140
|
+
openToAll: { reason: "test handler callable by any signed-in test user" },
|
|
141
|
+
});
|
|
140
142
|
|
|
141
143
|
const chunks: string[] = [];
|
|
142
144
|
for await (const chunk of stored!.handler(
|
|
@@ -225,7 +227,7 @@ describe("defineFeature", () => {
|
|
|
225
227
|
const _id: number = query.payload.userId;
|
|
226
228
|
return { id: _id, email: "test@test.de" };
|
|
227
229
|
},
|
|
228
|
-
{ access: { openToAll:
|
|
230
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
229
231
|
);
|
|
230
232
|
});
|
|
231
233
|
|
|
@@ -239,7 +241,7 @@ describe("defineFeature", () => {
|
|
|
239
241
|
handler: async () => {
|
|
240
242
|
return [{ id: 1, email: "test@test.de" }];
|
|
241
243
|
},
|
|
242
|
-
access: { openToAll:
|
|
244
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
243
245
|
});
|
|
244
246
|
|
|
245
247
|
const feature = defineFeature("test", (r) => {
|
|
@@ -402,12 +404,12 @@ describe("createRegistry", () => {
|
|
|
402
404
|
test("looks up handlers across features", () => {
|
|
403
405
|
const f1 = defineFeature("admin", (r) => {
|
|
404
406
|
r.writeHandler("user:invite", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
405
|
-
access: { openToAll:
|
|
407
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
406
408
|
});
|
|
407
409
|
});
|
|
408
410
|
const f2 = defineFeature("profile", (r) => {
|
|
409
411
|
r.queryHandler("profile:me", z.object({}), async () => ({ id: 1 }), {
|
|
410
|
-
access: { openToAll:
|
|
412
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
411
413
|
});
|
|
412
414
|
});
|
|
413
415
|
|
|
@@ -442,12 +444,12 @@ describe("createRegistry", () => {
|
|
|
442
444
|
test("different features can have same handler short name (prefixed differently)", () => {
|
|
443
445
|
const f1 = defineFeature("a", (r) => {
|
|
444
446
|
r.writeHandler("user:invite", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
445
|
-
access: { openToAll:
|
|
447
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
446
448
|
});
|
|
447
449
|
});
|
|
448
450
|
const f2 = defineFeature("b", (r) => {
|
|
449
451
|
r.writeHandler("user:invite", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
450
|
-
access: { openToAll:
|
|
452
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
451
453
|
});
|
|
452
454
|
});
|
|
453
455
|
|
|
@@ -513,7 +515,7 @@ describe("createRegistry", () => {
|
|
|
513
515
|
);
|
|
514
516
|
// Handler name "promote" has no entity prefix → can't be mapped
|
|
515
517
|
r.writeHandler("promote", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
516
|
-
access: { openToAll:
|
|
518
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
517
519
|
});
|
|
518
520
|
});
|
|
519
521
|
|
|
@@ -531,7 +533,7 @@ describe("createRegistry", () => {
|
|
|
531
533
|
);
|
|
532
534
|
// No field-access rules on entity → "reset" without entity prefix is fine
|
|
533
535
|
r.writeHandler("reset", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
534
|
-
access: { openToAll:
|
|
536
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
535
537
|
});
|
|
536
538
|
});
|
|
537
539
|
|
|
@@ -562,7 +564,7 @@ describe("createRegistry", () => {
|
|
|
562
564
|
isSuccess: true,
|
|
563
565
|
data: null,
|
|
564
566
|
}),
|
|
565
|
-
{ access: { openToAll:
|
|
567
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
566
568
|
);
|
|
567
569
|
});
|
|
568
570
|
|
|
@@ -591,10 +593,12 @@ describe("createRegistry", () => {
|
|
|
591
593
|
isSuccess: true,
|
|
592
594
|
data: null,
|
|
593
595
|
}),
|
|
594
|
-
{ access: { openToAll:
|
|
596
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
595
597
|
);
|
|
596
598
|
// Typo: "emp" instead of "employee"
|
|
597
|
-
r.queryHandler("emp:list", z.object({}), async () => [], {
|
|
599
|
+
r.queryHandler("emp:list", z.object({}), async () => [], {
|
|
600
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
601
|
+
});
|
|
598
602
|
});
|
|
599
603
|
|
|
600
604
|
expect(() => createRegistry([feature])).toThrow(/emp:list.*entity-bound.*no matching entity/i);
|
|
@@ -622,13 +626,15 @@ describe("createRegistry", () => {
|
|
|
622
626
|
isSuccess: true,
|
|
623
627
|
data: null,
|
|
624
628
|
}),
|
|
625
|
-
{ access: { openToAll:
|
|
629
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
626
630
|
);
|
|
627
631
|
// Standalone queries — no dot, intentionally not entity-bound
|
|
628
632
|
r.queryHandler("dashboard", z.object({}), async () => ({ total: 42 }), {
|
|
629
|
-
access: { openToAll:
|
|
633
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
634
|
+
});
|
|
635
|
+
r.queryHandler("orgChart", z.object({}), async () => [], {
|
|
636
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
630
637
|
});
|
|
631
|
-
r.queryHandler("orgChart", z.object({}), async () => [], { access: { openToAll: true } });
|
|
632
638
|
});
|
|
633
639
|
|
|
634
640
|
expect(() => createRegistry([feature])).not.toThrow();
|
|
@@ -924,12 +930,20 @@ describe("hasAccess", () => {
|
|
|
924
930
|
|
|
925
931
|
test("openToAll grants access to any authenticated user", () => {
|
|
926
932
|
const user = createTestUser({ roles: ["Employee"] });
|
|
927
|
-
expect(
|
|
933
|
+
expect(
|
|
934
|
+
hasAccess(user, {
|
|
935
|
+
openToAll: { reason: "test handler callable by any signed-in test user" },
|
|
936
|
+
}),
|
|
937
|
+
).toBe(true);
|
|
928
938
|
});
|
|
929
939
|
|
|
930
940
|
test("openToAll grants access even to user with no roles", () => {
|
|
931
941
|
const user = createTestUser({ roles: [] });
|
|
932
|
-
expect(
|
|
942
|
+
expect(
|
|
943
|
+
hasAccess(user, {
|
|
944
|
+
openToAll: { reason: "test handler callable by any signed-in test user" },
|
|
945
|
+
}),
|
|
946
|
+
).toBe(true);
|
|
933
947
|
});
|
|
934
948
|
|
|
935
949
|
test.each([{ openToAll: false }, { openToAll: {} }])(
|
|
@@ -2105,10 +2119,10 @@ describe("registry boot validation", () => {
|
|
|
2105
2119
|
const feature = defineFeature("shop", (r) => {
|
|
2106
2120
|
r.entity("order", createEntity({ table: "Orders", fields: {} }));
|
|
2107
2121
|
r.writeHandler("order:create", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
2108
|
-
access: { openToAll:
|
|
2122
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
2109
2123
|
});
|
|
2110
2124
|
r.writeHandler("order:cancel", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
2111
|
-
access: { openToAll:
|
|
2125
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
2112
2126
|
});
|
|
2113
2127
|
// Ein Job-Body, zwei Trigger — DRY-Pattern für Fanout-Cases.
|
|
2114
2128
|
r.job(
|
|
@@ -2129,7 +2143,7 @@ describe("registry boot validation", () => {
|
|
|
2129
2143
|
const feature = defineFeature("shop", (r) => {
|
|
2130
2144
|
r.entity("order", createEntity({ table: "Orders", fields: {} }));
|
|
2131
2145
|
r.writeHandler("order:create", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
2132
|
-
access: { openToAll:
|
|
2146
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
2133
2147
|
});
|
|
2134
2148
|
// create existiert, cancel nicht — zweiter Trigger ist Geist
|
|
2135
2149
|
r.job(
|
|
@@ -2153,7 +2167,7 @@ describe("registry boot validation", () => {
|
|
|
2153
2167
|
const feature = defineFeature("test", (r) => {
|
|
2154
2168
|
r.entity("item", createEntity({ table: "Items", fields: {} }));
|
|
2155
2169
|
r.writeHandler("item.create", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
2156
|
-
access: { openToAll:
|
|
2170
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
2157
2171
|
});
|
|
2158
2172
|
r.hook("postSave", "item.create", async () => {});
|
|
2159
2173
|
});
|
|
@@ -149,9 +149,11 @@ describe("defineEntityQueryHandler", () => {
|
|
|
149
149
|
|
|
150
150
|
test("access option is forwarded", () => {
|
|
151
151
|
const def = defineEntityListHandler("note", noteEntity, {
|
|
152
|
-
access: { openToAll:
|
|
152
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
153
|
+
});
|
|
154
|
+
expect(def.access).toEqual({
|
|
155
|
+
openToAll: { reason: "test handler callable by any signed-in test user" },
|
|
153
156
|
});
|
|
154
|
-
expect(def.access).toEqual({ openToAll: true });
|
|
155
157
|
});
|
|
156
158
|
});
|
|
157
159
|
|
|
@@ -180,10 +182,12 @@ describe("defineProjectionQueryHandler", () => {
|
|
|
180
182
|
const def = defineProjectionQueryHandler(
|
|
181
183
|
"revenue:list",
|
|
182
184
|
"showcase:projection:customer-revenue",
|
|
183
|
-
{ access: { openToAll:
|
|
185
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
184
186
|
);
|
|
185
187
|
expect(def.name).toBe("revenue:list");
|
|
186
|
-
expect(def.access).toEqual({
|
|
188
|
+
expect(def.access).toEqual({
|
|
189
|
+
openToAll: { reason: "test handler callable by any signed-in test user" },
|
|
190
|
+
});
|
|
187
191
|
// Empty-object schema — handler takes no payload fields.
|
|
188
192
|
expect(def.schema.safeParse({}).success).toBe(true);
|
|
189
193
|
});
|
|
@@ -192,7 +196,7 @@ describe("defineProjectionQueryHandler", () => {
|
|
|
192
196
|
const def = defineProjectionQueryHandler(
|
|
193
197
|
"revenue:list",
|
|
194
198
|
"showcase:projection:customer-revenue",
|
|
195
|
-
{ access: { openToAll:
|
|
199
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
196
200
|
);
|
|
197
201
|
const fakeRows = [{ customer: "a", totalCents: 100 }];
|
|
198
202
|
const ctx = {
|
|
@@ -215,7 +219,10 @@ describe("defineProjectionQueryHandler", () => {
|
|
|
215
219
|
const def = defineProjectionQueryHandler(
|
|
216
220
|
"revenue:list",
|
|
217
221
|
"showcase:projection:customer-revenue",
|
|
218
|
-
{
|
|
222
|
+
{
|
|
223
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
224
|
+
unsafeAllTenants: true,
|
|
225
|
+
},
|
|
219
226
|
);
|
|
220
227
|
const ctx = { queryProjection: mock().mockResolvedValue([]) };
|
|
221
228
|
await def.handler(
|
|
@@ -328,7 +335,9 @@ describe("registerEntityCrud", () => {
|
|
|
328
335
|
const { r, entities, writes, queries } = createCrudRegistrarMock();
|
|
329
336
|
registerEntityCrud(r, "note", noteEntitySoftDelete, {
|
|
330
337
|
write: { access: { roles: ["Admin"] } },
|
|
331
|
-
read: {
|
|
338
|
+
read: {
|
|
339
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
340
|
+
},
|
|
332
341
|
});
|
|
333
342
|
expect(entities["note"]).toBe(noteEntitySoftDelete);
|
|
334
343
|
expect(writes.map((w) => w.name)).toEqual([
|
|
@@ -339,7 +348,9 @@ describe("registerEntityCrud", () => {
|
|
|
339
348
|
]);
|
|
340
349
|
expect(queries.map((q) => q.name)).toEqual(["note:list", "note:detail"]);
|
|
341
350
|
expect(writes[0]?.access).toEqual({ roles: ["Admin"] });
|
|
342
|
-
expect(queries[0]?.access).toEqual({
|
|
351
|
+
expect(queries[0]?.access).toEqual({
|
|
352
|
+
openToAll: { reason: "test handler callable by any signed-in test user" },
|
|
353
|
+
});
|
|
343
354
|
});
|
|
344
355
|
|
|
345
356
|
test("verbs.delete: false skips delete handler", () => {
|
|
@@ -50,7 +50,7 @@ const throwOnPreSave: import("../types").PreSaveHookFn = async () => {
|
|
|
50
50
|
const contactFeature = defineFeature("presave-wiring", (r) => {
|
|
51
51
|
r.crud("contact", contactEntity, {
|
|
52
52
|
write: { access: { roles: ["User"] } },
|
|
53
|
-
read: { access: { openToAll:
|
|
53
|
+
read: { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
// preSave has no entity-wide `{ allOf }` shorthand (unlike postSave/
|
|
@@ -70,7 +70,7 @@ const throwingEntity = createEntity({
|
|
|
70
70
|
const throwingFeature = defineFeature("presave-wiring-throw", (r) => {
|
|
71
71
|
r.crud("thing", throwingEntity, {
|
|
72
72
|
write: { access: { roles: ["User"] } },
|
|
73
|
-
read: { access: { openToAll:
|
|
73
|
+
read: { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
74
74
|
});
|
|
75
75
|
r.hook("preSave", "thing:create", throwOnPreSave);
|
|
76
76
|
});
|
|
@@ -142,7 +142,7 @@ describe("extendsRegistrar", () => {
|
|
|
142
142
|
isSuccess: true as const,
|
|
143
143
|
data: { id: "c1" },
|
|
144
144
|
}),
|
|
145
|
-
{ access: { openToAll:
|
|
145
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
146
146
|
);
|
|
147
147
|
r.useExtension("audited", "credit");
|
|
148
148
|
});
|
|
@@ -181,7 +181,7 @@ describe("extendsRegistrar", () => {
|
|
|
181
181
|
isSuccess: true as const,
|
|
182
182
|
data: { id: "v1" },
|
|
183
183
|
}),
|
|
184
|
-
{ access: { openToAll:
|
|
184
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
185
185
|
);
|
|
186
186
|
r.writeHandler(
|
|
187
187
|
"vehicle:update",
|
|
@@ -190,7 +190,7 @@ describe("extendsRegistrar", () => {
|
|
|
190
190
|
isSuccess: true as const,
|
|
191
191
|
data: { id: "v1" },
|
|
192
192
|
}),
|
|
193
|
-
{ access: { openToAll:
|
|
193
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
194
194
|
);
|
|
195
195
|
r.useExtension("audited", "vehicle");
|
|
196
196
|
});
|
|
@@ -11,7 +11,9 @@ const taskEntity = createEntity({
|
|
|
11
11
|
describe("r.crud", () => {
|
|
12
12
|
test("registers the entity + full CRUD handler set, same as registerEntityCrud", () => {
|
|
13
13
|
const write = { access: { roles: ["Admin"] } } as const;
|
|
14
|
-
const read = {
|
|
14
|
+
const read = {
|
|
15
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
16
|
+
} as const;
|
|
15
17
|
|
|
16
18
|
const feature = defineFeature("via-crud", (r) => {
|
|
17
19
|
r.crud("task", taskEntity, { write, read });
|
|
@@ -28,7 +30,9 @@ describe("r.crud", () => {
|
|
|
28
30
|
|
|
29
31
|
test("without verbAccess, write handlers keep write.access (never fall back to the broader read.access)", () => {
|
|
30
32
|
const write = { access: { roles: ["Manager"] } } as const;
|
|
31
|
-
const read = {
|
|
33
|
+
const read = {
|
|
34
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
35
|
+
} as const;
|
|
32
36
|
|
|
33
37
|
const feature = defineFeature("via-crud-no-verb-access", (r) => {
|
|
34
38
|
r.crud("task", taskEntity, { write, read });
|
|
@@ -44,7 +48,9 @@ describe("r.crud", () => {
|
|
|
44
48
|
|
|
45
49
|
test("verbAccess overrides access per verb, other verbs keep write/read.access", () => {
|
|
46
50
|
const write = { access: { roles: ["Manager"] } } as const;
|
|
47
|
-
const read = {
|
|
51
|
+
const read = {
|
|
52
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
53
|
+
} as const;
|
|
48
54
|
const deleteAccess = { roles: ["Operator"] } as const;
|
|
49
55
|
const restoreAccess = { roles: ["Operator"] } as const;
|
|
50
56
|
const listAccess = { roles: ["Auditor"] } as const;
|
|
@@ -13,7 +13,7 @@ const propertyEntity = createEntity({
|
|
|
13
13
|
const propertyFeature = defineFeature("crud-verb-access", (r) => {
|
|
14
14
|
r.crud("property", propertyEntity, {
|
|
15
15
|
write: { access: { roles: ["User"] } },
|
|
16
|
-
read: { access: { openToAll:
|
|
16
|
+
read: { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
17
17
|
verbAccess: {
|
|
18
18
|
delete: { roles: ["Admin"] },
|
|
19
19
|
restore: { roles: ["Admin"] },
|
|
@@ -27,7 +27,7 @@ describe("HookPhases defaults", () => {
|
|
|
27
27
|
const feature = defineFeature("test", (r) => {
|
|
28
28
|
r.entity("thing", createEntity({ table: "things", fields: {} }));
|
|
29
29
|
r.writeHandler("thing:create", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
30
|
-
access: { openToAll:
|
|
30
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
31
31
|
});
|
|
32
32
|
r.hook("postSave", "thing:create", noopSave);
|
|
33
33
|
});
|
|
@@ -41,7 +41,7 @@ describe("HookPhases defaults", () => {
|
|
|
41
41
|
const feature = defineFeature("test", (r) => {
|
|
42
42
|
r.entity("thing", createEntity({ table: "things", fields: {} }));
|
|
43
43
|
r.writeHandler("thing:create", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
44
|
-
access: { openToAll:
|
|
44
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
45
45
|
});
|
|
46
46
|
r.hook("postSave", "thing:create", noopSave, { phase: HookPhases.inTransaction });
|
|
47
47
|
});
|
|
@@ -60,7 +60,7 @@ describe("HookPhases defaults", () => {
|
|
|
60
60
|
isSuccess: true,
|
|
61
61
|
data: null,
|
|
62
62
|
}),
|
|
63
|
-
{ access: { openToAll:
|
|
63
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
64
64
|
);
|
|
65
65
|
r.hook("preDelete", "thing:delete", async () => undefined);
|
|
66
66
|
});
|
|
@@ -95,7 +95,7 @@ describe("Registry phase filtering", () => {
|
|
|
95
95
|
const feature = defineFeature("test", (r) => {
|
|
96
96
|
r.entity("thing", createEntity({ table: "things", fields: {} }));
|
|
97
97
|
r.writeHandler("thing:create", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
98
|
-
access: { openToAll:
|
|
98
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
99
99
|
});
|
|
100
100
|
r.hook("postSave", "thing:create", inTxFn, { phase: HookPhases.inTransaction });
|
|
101
101
|
r.hook("postSave", "thing:create", afterFn); // default afterCommit
|
|
@@ -127,7 +127,7 @@ describe("Registry phase filtering", () => {
|
|
|
127
127
|
const feature = defineFeature("test", (r) => {
|
|
128
128
|
r.entity("thing", createEntity({ table: "things", fields: {} }));
|
|
129
129
|
r.writeHandler("thing:create", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
130
|
-
access: { openToAll:
|
|
130
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
131
131
|
});
|
|
132
132
|
});
|
|
133
133
|
|
|
@@ -69,7 +69,9 @@ describe("lifecycle hooks in registry", () => {
|
|
|
69
69
|
test("merges preSave hooks within same feature", () => {
|
|
70
70
|
const f1 = defineFeature("a", (r) => {
|
|
71
71
|
r.entity("user", createEntity({ table: "Users", fields: {} }));
|
|
72
|
-
r.writeHandler("user", z.object({}), stubHandler, {
|
|
72
|
+
r.writeHandler("user", z.object({}), stubHandler, {
|
|
73
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
74
|
+
});
|
|
73
75
|
r.hook("preSave", "user", async (changes) => changes);
|
|
74
76
|
r.hook("preSave", "user", async (changes) => changes);
|
|
75
77
|
});
|
|
@@ -81,12 +83,16 @@ describe("lifecycle hooks in registry", () => {
|
|
|
81
83
|
test("cross-feature hooks use full prefixed name", () => {
|
|
82
84
|
const f1 = defineFeature("a", (r) => {
|
|
83
85
|
r.entity("user", createEntity({ table: "Users", fields: {} }));
|
|
84
|
-
r.writeHandler("user", z.object({}), stubHandler, {
|
|
86
|
+
r.writeHandler("user", z.object({}), stubHandler, {
|
|
87
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
88
|
+
});
|
|
85
89
|
r.hook("postSave", "user", async () => {});
|
|
86
90
|
});
|
|
87
91
|
// Feature b hooks into a.user by using the full prefixed name
|
|
88
92
|
const f2 = defineFeature("b", (r) => {
|
|
89
|
-
r.writeHandler("a.user", z.object({}), stubHandler, {
|
|
93
|
+
r.writeHandler("a.user", z.object({}), stubHandler, {
|
|
94
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
95
|
+
});
|
|
90
96
|
r.hook("postSave", "a.user", async () => {});
|
|
91
97
|
r.hook("postSave", "a.user", async () => {});
|
|
92
98
|
});
|
|
@@ -28,7 +28,7 @@ const LIST_QN = "checklist:query:equipment:list";
|
|
|
28
28
|
const checklistFeature = defineFeature("checklist", (r) => {
|
|
29
29
|
r.crud("equipment", equipmentEntity, {
|
|
30
30
|
write: { access: { roles: ["Admin"] } },
|
|
31
|
-
read: { access: { openToAll:
|
|
31
|
+
read: { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
32
32
|
});
|
|
33
33
|
});
|
|
34
34
|
|
|
@@ -24,7 +24,9 @@ describe("postQuery hook registration", () => {
|
|
|
24
24
|
test("r.hook('postQuery', handlerQn, fn) lands in handler-keyed lifecycleHooks map", () => {
|
|
25
25
|
const feature = defineFeature("test", (r) => {
|
|
26
26
|
r.entity("thing", createEntity({ table: "things", fields: {} }));
|
|
27
|
-
r.queryHandler("thing:list", z.object({}), async () => [], {
|
|
27
|
+
r.queryHandler("thing:list", z.object({}), async () => [], {
|
|
28
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
29
|
+
});
|
|
28
30
|
r.hook("postQuery", "thing:list", noop);
|
|
29
31
|
});
|
|
30
32
|
|
|
@@ -70,7 +72,9 @@ describe("Registry getters", () => {
|
|
|
70
72
|
|
|
71
73
|
const feature = defineFeature("test", (r) => {
|
|
72
74
|
r.entity("thing", createEntity({ table: "things", fields: {} }));
|
|
73
|
-
r.queryHandler("thing:list", z.object({}), async () => [], {
|
|
75
|
+
r.queryHandler("thing:list", z.object({}), async () => [], {
|
|
76
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
77
|
+
});
|
|
74
78
|
r.hook("postQuery", "thing:list", fn);
|
|
75
79
|
});
|
|
76
80
|
|
|
@@ -42,13 +42,13 @@ describe("validateBoot — projectionDetail actions (fw#2166)", () => {
|
|
|
42
42
|
test("valid navigate + writeHandler actions boot cleanly", () => {
|
|
43
43
|
const feature = defineFeature("app", (r) => {
|
|
44
44
|
r.queryHandler("rent:detail", z.object({}), async () => ({ description: "x" }), {
|
|
45
|
-
access: { openToAll:
|
|
45
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
46
46
|
});
|
|
47
47
|
r.writeHandler(
|
|
48
48
|
"archive",
|
|
49
49
|
z.object({ id: z.string() }),
|
|
50
50
|
async () => ({ isSuccess: true as const, data: {} }),
|
|
51
|
-
{ access: { openToAll:
|
|
51
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
52
52
|
);
|
|
53
53
|
r.screen({
|
|
54
54
|
id: "rent-detail",
|
|
@@ -21,7 +21,7 @@ describe("validateBoot — projectionDetail singleton (fw#2312)", () => {
|
|
|
21
21
|
test("singleton + idParam throws", () => {
|
|
22
22
|
const feature = defineFeature("app", (r) => {
|
|
23
23
|
r.queryHandler("me", z.object({}), async () => ({ id: "u1" }), {
|
|
24
|
-
access: { openToAll:
|
|
24
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
25
25
|
});
|
|
26
26
|
r.screen({
|
|
27
27
|
id: "profile",
|
|
@@ -46,7 +46,7 @@ describe("validateBoot — projectionDetail singleton (fw#2312)", () => {
|
|
|
46
46
|
}),
|
|
47
47
|
);
|
|
48
48
|
r.queryHandler("me", z.object({}), async () => ({ id: "u1" }), {
|
|
49
|
-
access: { openToAll:
|
|
49
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
50
50
|
});
|
|
51
51
|
r.screen({
|
|
52
52
|
id: "profile",
|
|
@@ -71,7 +71,7 @@ describe("validateBoot — projectionDetail singleton (fw#2312)", () => {
|
|
|
71
71
|
test("singleton without idParam/detailFor boots cleanly", () => {
|
|
72
72
|
const feature = defineFeature("app", (r) => {
|
|
73
73
|
r.queryHandler("me", z.object({}), async () => ({ id: "u1" }), {
|
|
74
|
-
access: { openToAll:
|
|
74
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
75
75
|
});
|
|
76
76
|
r.screen({
|
|
77
77
|
id: "profile",
|
|
@@ -140,7 +140,7 @@ describe("validateBoot — projectionDetail tabs (fw record-layout)", () => {
|
|
|
140
140
|
test("object-form metric with its own label needs no fieldLabels entry", () => {
|
|
141
141
|
const feature = defineFeature("app", (r) => {
|
|
142
142
|
r.queryHandler("rent:detail", z.object({}), async () => ({ description: "x" }), {
|
|
143
|
-
access: { openToAll:
|
|
143
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
144
144
|
});
|
|
145
145
|
r.screen({
|
|
146
146
|
id: "rent-detail",
|
|
@@ -192,7 +192,7 @@ describe("validateBoot — projectionDetail tabs (fw record-layout)", () => {
|
|
|
192
192
|
test("metric navigate.tab with no screen/entity — a known section id boots cleanly", () => {
|
|
193
193
|
const feature = defineFeature("app", (r) => {
|
|
194
194
|
r.queryHandler("rent:detail", z.object({}), async () => ({ description: "x" }), {
|
|
195
|
-
access: { openToAll:
|
|
195
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
196
196
|
});
|
|
197
197
|
r.screen({
|
|
198
198
|
id: "rent-detail",
|
|
@@ -214,7 +214,7 @@ describe("validateBoot — projectionDetail tabs (fw record-layout)", () => {
|
|
|
214
214
|
test("valid tabs + header + metrics declaration boots cleanly", () => {
|
|
215
215
|
const feature = defineFeature("app", (r) => {
|
|
216
216
|
r.queryHandler("rent:detail", z.object({}), async () => ({ description: "x" }), {
|
|
217
|
-
access: { openToAll:
|
|
217
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
218
218
|
});
|
|
219
219
|
r.screen({
|
|
220
220
|
id: "rent-detail",
|
|
@@ -37,7 +37,7 @@ const LIST_QN = "roster:query:member:list";
|
|
|
37
37
|
const rosterFeature = defineFeature("roster", (r) => {
|
|
38
38
|
r.crud("member", memberEntity, {
|
|
39
39
|
write: { access: { roles: ["Admin"] } },
|
|
40
|
-
read: { access: { openToAll:
|
|
40
|
+
read: { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
41
41
|
});
|
|
42
42
|
r.screen({
|
|
43
43
|
id: "member-list",
|