@pylonsync/sdk 0.3.167 → 0.3.168
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/package.json +1 -1
- package/src/index.ts +84 -14
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -325,7 +325,12 @@ export function action(
|
|
|
325
325
|
// ---------------------------------------------------------------------------
|
|
326
326
|
|
|
327
327
|
export interface PolicyDefinition {
|
|
328
|
-
name
|
|
328
|
+
/** Optional — `buildManifest` auto-generates a name from the entity
|
|
329
|
+
* + a counter when the fluent `.policies(policy({...}))` chain
|
|
330
|
+
* omits one. Explicit names are still recommended for the
|
|
331
|
+
* procedural API since they appear in policy-denied error
|
|
332
|
+
* messages. */
|
|
333
|
+
name?: string;
|
|
329
334
|
entity?: string;
|
|
330
335
|
action?: string;
|
|
331
336
|
/**
|
|
@@ -580,8 +585,18 @@ export function actionsToManifest(
|
|
|
580
585
|
export function policiesToManifest(
|
|
581
586
|
policies: PolicyDefinition[]
|
|
582
587
|
): ManifestPolicy[] {
|
|
583
|
-
return policies.map((p) => {
|
|
584
|
-
const result: ManifestPolicy = {
|
|
588
|
+
return policies.map((p, i) => {
|
|
589
|
+
const result: ManifestPolicy = {
|
|
590
|
+
// Final-resort name autogen. `buildManifest` upstream already
|
|
591
|
+
// names every attached-via-fluent policy, but a caller passing
|
|
592
|
+
// `policies: [policy({ allowRead: "..." })]` to a custom
|
|
593
|
+
// manifest builder would slip through with `name: undefined`.
|
|
594
|
+
// Stamp a unique fallback so the runtime never sees a blank.
|
|
595
|
+
name:
|
|
596
|
+
p.name && p.name.length > 0
|
|
597
|
+
? p.name
|
|
598
|
+
: `${(p.entity ?? p.action ?? "unnamed").toLowerCase()}_p${i}`,
|
|
599
|
+
};
|
|
585
600
|
if (p.allow) result.allow = p.allow;
|
|
586
601
|
if (p.allowRead) result.allowRead = p.allowRead;
|
|
587
602
|
if (p.allowInsert) result.allowInsert = p.allowInsert;
|
|
@@ -759,6 +774,33 @@ export function buildManifest(options: {
|
|
|
759
774
|
policies?: PolicyDefinition[];
|
|
760
775
|
auth?: ManifestAuthConfig;
|
|
761
776
|
}): AppManifest {
|
|
777
|
+
// Pull policies attached via the fluent `e.entity().policies(...)`
|
|
778
|
+
// chain onto the top-level policies list. Without this, fluent
|
|
779
|
+
// apps would register entities without policies and every read
|
|
780
|
+
// would default-deny. Existing apps using the procedural API
|
|
781
|
+
// (`entity()` + separate `policy({...})` exports) are unaffected
|
|
782
|
+
// because `extractAttachedPolicies` returns an empty array for
|
|
783
|
+
// them. Concat order: top-level policies first (explicit beats
|
|
784
|
+
// attached), then anything pulled off entities.
|
|
785
|
+
//
|
|
786
|
+
// Stamp a name if the fluent caller omitted it — `name` is
|
|
787
|
+
// technically required on PolicyDefinition but the docs imply you
|
|
788
|
+
// can `.policies(policy({ allowRead: "..." }))` without one. Auto-
|
|
789
|
+
// derive from the entity + a counter so two attached policies
|
|
790
|
+
// don't collide.
|
|
791
|
+
const attached: PolicyDefinition[] = [];
|
|
792
|
+
for (const ent of options.entities) {
|
|
793
|
+
const extracted = extractAttachedPolicies(ent);
|
|
794
|
+
extracted.forEach((p, i) => {
|
|
795
|
+
attached.push({
|
|
796
|
+
...p,
|
|
797
|
+
name: p.name && p.name.length > 0
|
|
798
|
+
? p.name
|
|
799
|
+
: `${(p.entity ?? ent.name).toLowerCase()}_attached_${i}`,
|
|
800
|
+
});
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
const allPolicies = [...(options.policies ?? []), ...attached];
|
|
762
804
|
return {
|
|
763
805
|
manifest_version: MANIFEST_VERSION,
|
|
764
806
|
name: options.name,
|
|
@@ -767,7 +809,7 @@ export function buildManifest(options: {
|
|
|
767
809
|
routes: routesToManifest(options.routes),
|
|
768
810
|
queries: queriesToManifest(options.queries ?? []),
|
|
769
811
|
actions: actionsToManifest(options.actions ?? []),
|
|
770
|
-
policies: policiesToManifest(
|
|
812
|
+
policies: policiesToManifest(allPolicies),
|
|
771
813
|
auth: options.auth ?? auth(),
|
|
772
814
|
};
|
|
773
815
|
}
|
|
@@ -943,19 +985,47 @@ declare module "./index" {
|
|
|
943
985
|
// surface declared above.)
|
|
944
986
|
}
|
|
945
987
|
|
|
946
|
-
//
|
|
947
|
-
//
|
|
948
|
-
//
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
988
|
+
// Field builder with chainable `.default()` / `.defaultNow()`. All
|
|
989
|
+
// other chainables (`optional`, `unique`, `crdt`, `serverOnly`,
|
|
990
|
+
// `readonly`) are reimplemented here to return another
|
|
991
|
+
// `buildFieldWithDefaults` — without this, calling `.optional()`
|
|
992
|
+
// after `.default()` would drop the default markers off the chain
|
|
993
|
+
// (codex Wave-3 review: the previous `{ ...base, default, defaultNow }`
|
|
994
|
+
// pattern delegated optional/unique to the original buildField which
|
|
995
|
+
// returned a builder lacking `.default()`). Recursion through the
|
|
996
|
+
// same constructor keeps the surface stable regardless of chain
|
|
997
|
+
// order.
|
|
998
|
+
function buildFieldWithDefaults(
|
|
999
|
+
def: FieldDefinition & {
|
|
1000
|
+
default?: DefaultMarker;
|
|
1001
|
+
enumValues?: readonly string[];
|
|
1002
|
+
},
|
|
1003
|
+
): FieldBuilder & {
|
|
1004
|
+
default(value: unknown): ReturnType<typeof buildFieldWithDefaults>;
|
|
1005
|
+
defaultNow(): ReturnType<typeof buildFieldWithDefaults>;
|
|
953
1006
|
} {
|
|
954
|
-
const base = __originalBuildField(def);
|
|
955
1007
|
return {
|
|
956
|
-
|
|
1008
|
+
_def: def,
|
|
1009
|
+
optional() {
|
|
1010
|
+
return buildFieldWithDefaults({ ...def, optional: true });
|
|
1011
|
+
},
|
|
1012
|
+
unique() {
|
|
1013
|
+
return buildFieldWithDefaults({ ...def, unique: true });
|
|
1014
|
+
},
|
|
1015
|
+
crdt(annotation) {
|
|
1016
|
+
return buildFieldWithDefaults({ ...def, crdt: annotation });
|
|
1017
|
+
},
|
|
1018
|
+
serverOnly() {
|
|
1019
|
+
return buildFieldWithDefaults({ ...def, serverOnly: true });
|
|
1020
|
+
},
|
|
1021
|
+
readonly() {
|
|
1022
|
+
return buildFieldWithDefaults({ ...def, readonly: true });
|
|
1023
|
+
},
|
|
957
1024
|
default(value: unknown) {
|
|
958
|
-
return buildFieldWithDefaults({
|
|
1025
|
+
return buildFieldWithDefaults({
|
|
1026
|
+
...def,
|
|
1027
|
+
default: { kind: "value", value },
|
|
1028
|
+
});
|
|
959
1029
|
},
|
|
960
1030
|
defaultNow() {
|
|
961
1031
|
return buildFieldWithDefaults({ ...def, default: { kind: "now" } });
|