@pylonsync/sdk 0.3.167 → 0.3.169
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 +112 -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
|
/**
|
|
@@ -390,6 +395,16 @@ export interface ManifestField {
|
|
|
390
395
|
/** Set when the field is `field.X().readonly()` — see
|
|
391
396
|
* [`FieldDefinition.readonly`]. Omitted by default. */
|
|
392
397
|
readonly?: boolean;
|
|
398
|
+
/** Default value to fill on insert when the row omits this field.
|
|
399
|
+
* - `"now"` → runtime stamps the current UTC time
|
|
400
|
+
* - any literal → runtime stamps that exact value
|
|
401
|
+
* Maps to `field.X().defaultNow()` / `.default(value)`. */
|
|
402
|
+
default?: "now" | string | number | boolean | null;
|
|
403
|
+
/** Allowed values for `field.enum([...])` — recorded so codegen
|
|
404
|
+
* can emit a literal-union type and runtime validation can
|
|
405
|
+
* reject out-of-set inserts. Plain `field.string()` doesn't
|
|
406
|
+
* carry this; only `field.enum()`. */
|
|
407
|
+
enumValues?: readonly string[];
|
|
393
408
|
}
|
|
394
409
|
|
|
395
410
|
export interface ManifestIndex {
|
|
@@ -499,6 +514,24 @@ export function entitiesToManifest(
|
|
|
499
514
|
if (fb._def.readonly) {
|
|
500
515
|
f.readonly = true;
|
|
501
516
|
}
|
|
517
|
+
// `default` + `enumValues` are surfaced on the fluent
|
|
518
|
+
// FieldBuilder via the v0.4 SDK. Read off the private
|
|
519
|
+
// backing slot so both APIs serialize identically — apps
|
|
520
|
+
// using the procedural `field` exports get the same
|
|
521
|
+
// ManifestField shape as fluent apps.
|
|
522
|
+
const extra = fb._def as FieldDefinition & {
|
|
523
|
+
default?: { kind: "value"; value: unknown } | { kind: "now" };
|
|
524
|
+
enumValues?: readonly string[];
|
|
525
|
+
};
|
|
526
|
+
if (extra.default) {
|
|
527
|
+
f.default =
|
|
528
|
+
extra.default.kind === "now"
|
|
529
|
+
? "now"
|
|
530
|
+
: (extra.default.value as ManifestField["default"]);
|
|
531
|
+
}
|
|
532
|
+
if (extra.enumValues && extra.enumValues.length > 0) {
|
|
533
|
+
f.enumValues = extra.enumValues;
|
|
534
|
+
}
|
|
502
535
|
return f;
|
|
503
536
|
}),
|
|
504
537
|
indexes: (e.indexes ?? []).map((idx) => ({
|
|
@@ -580,8 +613,18 @@ export function actionsToManifest(
|
|
|
580
613
|
export function policiesToManifest(
|
|
581
614
|
policies: PolicyDefinition[]
|
|
582
615
|
): ManifestPolicy[] {
|
|
583
|
-
return policies.map((p) => {
|
|
584
|
-
const result: ManifestPolicy = {
|
|
616
|
+
return policies.map((p, i) => {
|
|
617
|
+
const result: ManifestPolicy = {
|
|
618
|
+
// Final-resort name autogen. `buildManifest` upstream already
|
|
619
|
+
// names every attached-via-fluent policy, but a caller passing
|
|
620
|
+
// `policies: [policy({ allowRead: "..." })]` to a custom
|
|
621
|
+
// manifest builder would slip through with `name: undefined`.
|
|
622
|
+
// Stamp a unique fallback so the runtime never sees a blank.
|
|
623
|
+
name:
|
|
624
|
+
p.name && p.name.length > 0
|
|
625
|
+
? p.name
|
|
626
|
+
: `${(p.entity ?? p.action ?? "unnamed").toLowerCase()}_p${i}`,
|
|
627
|
+
};
|
|
585
628
|
if (p.allow) result.allow = p.allow;
|
|
586
629
|
if (p.allowRead) result.allowRead = p.allowRead;
|
|
587
630
|
if (p.allowInsert) result.allowInsert = p.allowInsert;
|
|
@@ -759,6 +802,33 @@ export function buildManifest(options: {
|
|
|
759
802
|
policies?: PolicyDefinition[];
|
|
760
803
|
auth?: ManifestAuthConfig;
|
|
761
804
|
}): AppManifest {
|
|
805
|
+
// Pull policies attached via the fluent `e.entity().policies(...)`
|
|
806
|
+
// chain onto the top-level policies list. Without this, fluent
|
|
807
|
+
// apps would register entities without policies and every read
|
|
808
|
+
// would default-deny. Existing apps using the procedural API
|
|
809
|
+
// (`entity()` + separate `policy({...})` exports) are unaffected
|
|
810
|
+
// because `extractAttachedPolicies` returns an empty array for
|
|
811
|
+
// them. Concat order: top-level policies first (explicit beats
|
|
812
|
+
// attached), then anything pulled off entities.
|
|
813
|
+
//
|
|
814
|
+
// Stamp a name if the fluent caller omitted it — `name` is
|
|
815
|
+
// technically required on PolicyDefinition but the docs imply you
|
|
816
|
+
// can `.policies(policy({ allowRead: "..." }))` without one. Auto-
|
|
817
|
+
// derive from the entity + a counter so two attached policies
|
|
818
|
+
// don't collide.
|
|
819
|
+
const attached: PolicyDefinition[] = [];
|
|
820
|
+
for (const ent of options.entities) {
|
|
821
|
+
const extracted = extractAttachedPolicies(ent);
|
|
822
|
+
extracted.forEach((p, i) => {
|
|
823
|
+
attached.push({
|
|
824
|
+
...p,
|
|
825
|
+
name: p.name && p.name.length > 0
|
|
826
|
+
? p.name
|
|
827
|
+
: `${(p.entity ?? ent.name).toLowerCase()}_attached_${i}`,
|
|
828
|
+
});
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
const allPolicies = [...(options.policies ?? []), ...attached];
|
|
762
832
|
return {
|
|
763
833
|
manifest_version: MANIFEST_VERSION,
|
|
764
834
|
name: options.name,
|
|
@@ -767,7 +837,7 @@ export function buildManifest(options: {
|
|
|
767
837
|
routes: routesToManifest(options.routes),
|
|
768
838
|
queries: queriesToManifest(options.queries ?? []),
|
|
769
839
|
actions: actionsToManifest(options.actions ?? []),
|
|
770
|
-
policies: policiesToManifest(
|
|
840
|
+
policies: policiesToManifest(allPolicies),
|
|
771
841
|
auth: options.auth ?? auth(),
|
|
772
842
|
};
|
|
773
843
|
}
|
|
@@ -943,19 +1013,47 @@ declare module "./index" {
|
|
|
943
1013
|
// surface declared above.)
|
|
944
1014
|
}
|
|
945
1015
|
|
|
946
|
-
//
|
|
947
|
-
//
|
|
948
|
-
//
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1016
|
+
// Field builder with chainable `.default()` / `.defaultNow()`. All
|
|
1017
|
+
// other chainables (`optional`, `unique`, `crdt`, `serverOnly`,
|
|
1018
|
+
// `readonly`) are reimplemented here to return another
|
|
1019
|
+
// `buildFieldWithDefaults` — without this, calling `.optional()`
|
|
1020
|
+
// after `.default()` would drop the default markers off the chain
|
|
1021
|
+
// (codex Wave-3 review: the previous `{ ...base, default, defaultNow }`
|
|
1022
|
+
// pattern delegated optional/unique to the original buildField which
|
|
1023
|
+
// returned a builder lacking `.default()`). Recursion through the
|
|
1024
|
+
// same constructor keeps the surface stable regardless of chain
|
|
1025
|
+
// order.
|
|
1026
|
+
function buildFieldWithDefaults(
|
|
1027
|
+
def: FieldDefinition & {
|
|
1028
|
+
default?: DefaultMarker;
|
|
1029
|
+
enumValues?: readonly string[];
|
|
1030
|
+
},
|
|
1031
|
+
): FieldBuilder & {
|
|
1032
|
+
default(value: unknown): ReturnType<typeof buildFieldWithDefaults>;
|
|
1033
|
+
defaultNow(): ReturnType<typeof buildFieldWithDefaults>;
|
|
953
1034
|
} {
|
|
954
|
-
const base = __originalBuildField(def);
|
|
955
1035
|
return {
|
|
956
|
-
|
|
1036
|
+
_def: def,
|
|
1037
|
+
optional() {
|
|
1038
|
+
return buildFieldWithDefaults({ ...def, optional: true });
|
|
1039
|
+
},
|
|
1040
|
+
unique() {
|
|
1041
|
+
return buildFieldWithDefaults({ ...def, unique: true });
|
|
1042
|
+
},
|
|
1043
|
+
crdt(annotation) {
|
|
1044
|
+
return buildFieldWithDefaults({ ...def, crdt: annotation });
|
|
1045
|
+
},
|
|
1046
|
+
serverOnly() {
|
|
1047
|
+
return buildFieldWithDefaults({ ...def, serverOnly: true });
|
|
1048
|
+
},
|
|
1049
|
+
readonly() {
|
|
1050
|
+
return buildFieldWithDefaults({ ...def, readonly: true });
|
|
1051
|
+
},
|
|
957
1052
|
default(value: unknown) {
|
|
958
|
-
return buildFieldWithDefaults({
|
|
1053
|
+
return buildFieldWithDefaults({
|
|
1054
|
+
...def,
|
|
1055
|
+
default: { kind: "value", value },
|
|
1056
|
+
});
|
|
959
1057
|
},
|
|
960
1058
|
defaultNow() {
|
|
961
1059
|
return buildFieldWithDefaults({ ...def, default: { kind: "now" } });
|