@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +84 -14
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.3.167",
6
+ "version": "0.3.168",
7
7
  "type": "module",
8
8
  "main": "src/index.ts",
9
9
  "types": "src/index.ts",
package/src/index.ts CHANGED
@@ -325,7 +325,12 @@ export function action(
325
325
  // ---------------------------------------------------------------------------
326
326
 
327
327
  export interface PolicyDefinition {
328
- name: string;
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 = { name: p.name };
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(options.policies ?? []),
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
- // Patch the existing buildField to add default markers. Apps reach
947
- // the chainables through any returned FieldBuilder — `.default(val)`
948
- // records a literal, `.defaultNow()` records the `now` marker.
949
- const __originalBuildField = buildField;
950
- function buildFieldWithDefaults(def: FieldDefinition & { default?: DefaultMarker }): FieldBuilder & {
951
- default(value: unknown): FieldBuilder;
952
- defaultNow(): FieldBuilder;
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
- ...base,
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({ ...def, default: { kind: "value", value } });
1025
+ return buildFieldWithDefaults({
1026
+ ...def,
1027
+ default: { kind: "value", value },
1028
+ });
959
1029
  },
960
1030
  defaultNow() {
961
1031
  return buildFieldWithDefaults({ ...def, default: { kind: "now" } });