@peers-app/peers-sdk 0.23.4 → 0.23.6

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 (75) hide show
  1. package/dist/context/data-context.d.ts +36 -5
  2. package/dist/context/data-context.js +44 -9
  3. package/dist/context/user-context.d.ts +44 -0
  4. package/dist/context/user-context.js +91 -12
  5. package/dist/contracts/__tests__/contract-provider-router.test.js +26 -0
  6. package/dist/contracts/__tests__/contract-resolvers.test.js +44 -0
  7. package/dist/contracts/__tests__/registry.test.js +104 -0
  8. package/dist/contracts/contract-provider-router.d.ts +21 -0
  9. package/dist/contracts/contract-provider-router.js +31 -5
  10. package/dist/contracts/contract-proxy.d.ts +3 -0
  11. package/dist/contracts/contract-proxy.js +3 -0
  12. package/dist/contracts/contract-resolvers.d.ts +21 -3
  13. package/dist/contracts/contract-resolvers.js +105 -32
  14. package/dist/contracts/index.d.ts +3 -3
  15. package/dist/contracts/registry.d.ts +59 -0
  16. package/dist/contracts/registry.js +155 -25
  17. package/dist/contracts/system-contracts.js +3 -1
  18. package/dist/data/index.d.ts +1 -0
  19. package/dist/data/index.js +1 -0
  20. package/dist/data/orm/types.d.ts +19 -0
  21. package/dist/data/orm/types.js +11 -0
  22. package/dist/data/package-table-identity.d.ts +46 -0
  23. package/dist/data/package-table-identity.js +71 -0
  24. package/dist/data/package-table-identity.test.d.ts +1 -0
  25. package/dist/data/package-table-identity.test.js +46 -0
  26. package/dist/data/package-version-resolver.d.ts +13 -0
  27. package/dist/data/package-version-resolver.js +142 -6
  28. package/dist/data/package-version-resolver.test.js +42 -0
  29. package/dist/data/persistent-var-identity.d.ts +37 -0
  30. package/dist/data/persistent-var-identity.js +69 -0
  31. package/dist/data/persistent-vars.d.ts +3 -5
  32. package/dist/data/persistent-vars.js +25 -13
  33. package/dist/data/persistent-vars.package-scope.test.d.ts +1 -0
  34. package/dist/data/persistent-vars.package-scope.test.js +27 -0
  35. package/dist/data/tools.d.ts +3 -3
  36. package/dist/package-installer/index.d.ts +1 -0
  37. package/dist/package-installer/index.js +1 -0
  38. package/dist/package-installer/package-cloner.d.ts +1 -1
  39. package/dist/package-installer/package-cloner.js +11 -16
  40. package/dist/package-installer/package-cloner.test.js +5 -3
  41. package/dist/package-installer/package-creator.d.ts +4 -3
  42. package/dist/package-installer/package-creator.js +13 -5
  43. package/dist/package-installer/package-creator.test.js +19 -0
  44. package/dist/package-installer/package-dev-tools.d.ts +30 -0
  45. package/dist/package-installer/package-dev-tools.js +63 -0
  46. package/dist/package-installer/package-dev-tools.test.d.ts +1 -0
  47. package/dist/package-installer/package-dev-tools.test.js +64 -0
  48. package/dist/package-loader/contract-package-loader.js +20 -8
  49. package/dist/package-loader/contract-package-loader.test.js +16 -0
  50. package/dist/package-loader/index.d.ts +4 -0
  51. package/dist/package-loader/index.js +4 -0
  52. package/dist/package-loader/isolated-artifact.d.ts +233 -0
  53. package/dist/package-loader/isolated-artifact.js +643 -0
  54. package/dist/package-loader/isolated-artifact.test.d.ts +1 -0
  55. package/dist/package-loader/isolated-artifact.test.js +270 -0
  56. package/dist/package-loader/isolated-persistent-var-gateway.d.ts +20 -0
  57. package/dist/package-loader/isolated-persistent-var-gateway.js +106 -0
  58. package/dist/package-loader/isolated-persistent-var-gateway.test.d.ts +1 -0
  59. package/dist/package-loader/isolated-persistent-var-gateway.test.js +56 -0
  60. package/dist/package-loader/isolated-runtime.d.ts +229 -0
  61. package/dist/package-loader/isolated-runtime.js +37 -0
  62. package/dist/package-loader/isolated-table-gateway.d.ts +16 -0
  63. package/dist/package-loader/isolated-table-gateway.js +375 -0
  64. package/dist/package-loader/isolated-table-gateway.test.d.ts +1 -0
  65. package/dist/package-loader/isolated-table-gateway.test.js +206 -0
  66. package/dist/package-loader/isolated-value.d.ts +14 -0
  67. package/dist/package-loader/isolated-value.js +63 -0
  68. package/dist/package-loader/package-loader.d.ts +123 -4
  69. package/dist/package-loader/package-loader.js +417 -46
  70. package/dist/package-loader/package-loader.test.js +478 -12
  71. package/dist/package-loader/resolve-consumed-contracts.d.ts +3 -3
  72. package/dist/package-loader/resolve-consumed-contracts.js +18 -10
  73. package/dist/utils.d.ts +21 -2
  74. package/dist/utils.js +46 -7
  75. package/package.json +1 -1
@@ -31,26 +31,139 @@ class ContractRegistry {
31
31
  * @returns Validation result. If invalid the provider is not registered.
32
32
  */
33
33
  register(providerPackageId, definition, alsoImplementsDecls = []) {
34
+ return this.registerBatch(providerPackageId, [
35
+ { definition, alsoImplements: alsoImplementsDecls },
36
+ ]);
37
+ }
38
+ /**
39
+ * Atomically register every contract supplied by one provider package.
40
+ *
41
+ * Frozen-shape and `alsoImplements` checks run against a candidate registry
42
+ * containing the whole batch, so declarations may reference another
43
+ * contract in the same install. If any entry fails, no definition, provider,
44
+ * active-provider pointer, or reverse index is changed.
45
+ *
46
+ * @param providerPackageId - Trusted identity of the provider package.
47
+ * @param registrations - Contract definitions and compatibility declarations.
48
+ * @returns The combined validation result.
49
+ */
50
+ registerBatch(providerPackageId, registrations) {
51
+ const validation = this.validateBatch(providerPackageId, registrations);
52
+ if (!validation.valid) {
53
+ return validation;
54
+ }
55
+ this.commitRegistrations(providerPackageId, registrations);
56
+ return validation;
57
+ }
58
+ /**
59
+ * Atomically replace the complete contract set for one provider package.
60
+ *
61
+ * Validation runs before the existing provider entries are touched. Active
62
+ * selections remain on this package for contract versions it still provides;
63
+ * removed versions fall back according to ordinary unregister semantics.
64
+ *
65
+ * @param providerPackageId - Trusted provider package identity.
66
+ * @param registrations - Complete replacement set.
67
+ * @returns Validation result; invalid replacements leave the registry unchanged.
68
+ */
69
+ replaceBatch(providerPackageId, registrations) {
70
+ const validation = this.validateBatch(providerPackageId, registrations);
71
+ if (!validation.valid) {
72
+ return validation;
73
+ }
74
+ const previouslyActive = new Set((this.packageContracts.get(providerPackageId) ?? []).filter((key) => this.activeProviders.get(key)?.providerPackageId === providerPackageId));
75
+ this.unregister(providerPackageId);
76
+ this.commitRegistrations(providerPackageId, registrations);
77
+ for (const key of previouslyActive) {
78
+ const provider = this.allProviders
79
+ .get(key)
80
+ ?.find((item) => item.providerPackageId === providerPackageId);
81
+ if (provider) {
82
+ this.activeProviders.set(key, provider);
83
+ }
84
+ }
85
+ return validation;
86
+ }
87
+ /**
88
+ * Validate a complete provider registration batch without changing registry state.
89
+ *
90
+ * @param providerPackageId - Trusted provider package identity.
91
+ * @param registrations - Definitions and compatibility declarations to validate.
92
+ * @returns Combined validation result.
93
+ */
94
+ validateBatch(providerPackageId, registrations) {
95
+ const candidateDefinitions = new Map(this.definitions);
96
+ const batchKeys = new Set();
97
+ for (const { definition } of registrations) {
98
+ const key = (0, types_1.contractKey)(definition.contractId, definition.version);
99
+ if (batchKeys.has(key)) {
100
+ return {
101
+ valid: false,
102
+ errors: [
103
+ {
104
+ kind: "contract",
105
+ name: key,
106
+ message: `Provider ${providerPackageId} registered contract ${key} more than once`,
107
+ },
108
+ ],
109
+ };
110
+ }
111
+ batchKeys.add(key);
112
+ const existingDef = this.definitions.get(key);
113
+ if (existingDef) {
114
+ const immutability = (0, validate_1.validateImmutability)(existingDef, definition);
115
+ if (!immutability.valid) {
116
+ return immutability;
117
+ }
118
+ }
119
+ candidateDefinitions.set(key, definition);
120
+ }
121
+ for (const { definition, alsoImplements = [] } of registrations) {
122
+ if (alsoImplements.length === 0) {
123
+ continue;
124
+ }
125
+ const compatibility = (0, validate_1.validateAlsoImplements)(definition, alsoImplements, (contractId, version) => candidateDefinitions.get((0, types_1.contractKey)(contractId, version)));
126
+ if (!compatibility.valid) {
127
+ return compatibility;
128
+ }
129
+ }
130
+ return { valid: true, errors: [] };
131
+ }
132
+ commitRegistrations(providerPackageId, registrations) {
133
+ const explicitKeys = new Set(registrations.map(({ definition }) => (0, types_1.contractKey)(definition.contractId, definition.version)));
134
+ for (const { definition } of registrations) {
135
+ this.commitProvider(providerPackageId, definition, definition);
136
+ }
137
+ for (const { definition, alsoImplements = [] } of registrations) {
138
+ for (const declaration of alsoImplements) {
139
+ const declaredVersion = declaration.version;
140
+ const versions = typeof declaredVersion === "number"
141
+ ? [declaredVersion]
142
+ : Array.from({ length: declaredVersion.to - declaredVersion.from + 1 }, (_, index) => declaredVersion.from + index);
143
+ for (const version of versions) {
144
+ const key = (0, types_1.contractKey)(declaration.contractId, version);
145
+ if (explicitKeys.has(key)) {
146
+ continue;
147
+ }
148
+ const target = this.definitions.get(key);
149
+ if (target) {
150
+ this.commitProvider(providerPackageId, target, definition);
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+ commitProvider(providerPackageId, definition, implementation) {
34
157
  const key = (0, types_1.contractKey)(definition.contractId, definition.version);
35
- // Immutability check against any previously stored definition
36
- const existingDef = this.definitions.get(key);
37
- if (existingDef) {
38
- const immResult = (0, validate_1.validateImmutability)(existingDef, definition);
39
- if (!immResult.valid)
40
- return immResult;
41
- }
42
- // alsoImplements check
43
- if (alsoImplementsDecls.length > 0) {
44
- const aiResult = (0, validate_1.validateAlsoImplements)(definition, alsoImplementsDecls, (cid, ver) => this.definitions.get((0, types_1.contractKey)(cid, ver)));
45
- if (!aiResult.valid)
46
- return aiResult;
47
- }
48
- // Store the definition (first-come for frozen, latest for dev)
49
158
  this.definitions.set(key, definition);
50
- const provider = { providerPackageId, definition };
51
- // Add to the all-providers list
159
+ const provider = {
160
+ providerPackageId,
161
+ definition,
162
+ implementationContractId: implementation.contractId,
163
+ implementationVersion: implementation.version,
164
+ };
52
165
  const providers = this.allProviders.get(key) ?? [];
53
- const existingIdx = providers.findIndex((p) => p.providerPackageId === providerPackageId);
166
+ const existingIdx = providers.findIndex((item) => item.providerPackageId === providerPackageId);
54
167
  if (existingIdx >= 0) {
55
168
  providers[existingIdx] = provider;
56
169
  }
@@ -58,17 +171,15 @@ class ContractRegistry {
58
171
  providers.push(provider);
59
172
  }
60
173
  this.allProviders.set(key, providers);
61
- // If no active provider, auto-activate
62
- if (!this.activeProviders.has(key)) {
174
+ const active = this.activeProviders.get(key);
175
+ if (!active || active.providerPackageId === providerPackageId) {
63
176
  this.activeProviders.set(key, provider);
64
177
  }
65
- // Update reverse index
66
- const pkgKeys = this.packageContracts.get(providerPackageId) ?? [];
67
- if (!pkgKeys.includes(key)) {
68
- pkgKeys.push(key);
178
+ const packageKeys = this.packageContracts.get(providerPackageId) ?? [];
179
+ if (!packageKeys.includes(key)) {
180
+ packageKeys.push(key);
69
181
  }
70
- this.packageContracts.set(providerPackageId, pkgKeys);
71
- return { valid: true, errors: [] };
182
+ this.packageContracts.set(providerPackageId, packageKeys);
72
183
  }
73
184
  /**
74
185
  * Switch the active provider for a contract to a different registered package.
@@ -106,6 +217,25 @@ class ContractRegistry {
106
217
  const key = (0, types_1.contractKey)(contractId, version);
107
218
  return (this.allProviders.get(key) ?? []).map((p) => p.providerPackageId);
108
219
  }
220
+ /**
221
+ * Resolve the concrete contract implementation selected for one provider.
222
+ *
223
+ * For a direct registration this matches the requested contract. For an
224
+ * `alsoImplements` alias it identifies the newer compatible implementation.
225
+ *
226
+ * @param contractId - Consumer-requested contract identity.
227
+ * @param version - Consumer-requested contract version.
228
+ * @param providerPackageId - Registry-selected provider package.
229
+ */
230
+ getProviderImplementation(contractId, version, providerPackageId) {
231
+ const provider = (this.allProviders.get((0, types_1.contractKey)(contractId, version)) ?? []).find((candidate) => candidate.providerPackageId === providerPackageId);
232
+ return provider
233
+ ? {
234
+ contractId: provider.implementationContractId,
235
+ version: provider.implementationVersion,
236
+ }
237
+ : undefined;
238
+ }
109
239
  /**
110
240
  * Pick a provider for a consumed-contract declaration.
111
241
  *
@@ -25,7 +25,9 @@ function registerSystemContract(registration) {
25
25
  const { contractId, version, resolve } = registration;
26
26
  queueMicrotask(() => {
27
27
  const { registerContractResolver } = require("./contract-resolvers");
28
- registerContractResolver(contractId, version, resolve);
28
+ registerContractResolver(contractId, version, resolve, {
29
+ providerPackageId: registration.providerPackageId,
30
+ });
29
31
  });
30
32
  }
31
33
  const key = registration.contractId && registration.version != null
@@ -13,6 +13,7 @@ export * from "./group-secret";
13
13
  export * from "./group-share";
14
14
  export * from "./groups";
15
15
  export * from "./messages";
16
+ export * from "./package-table-identity";
16
17
  export * from "./package-version-resolver";
17
18
  export * from "./package-versions";
18
19
  export * from "./packages";
@@ -29,6 +29,7 @@ __exportStar(require("./group-secret"), exports);
29
29
  __exportStar(require("./group-share"), exports);
30
30
  __exportStar(require("./groups"), exports);
31
31
  __exportStar(require("./messages"), exports);
32
+ __exportStar(require("./package-table-identity"), exports);
32
33
  __exportStar(require("./package-version-resolver"), exports);
33
34
  __exportStar(require("./package-versions"), exports);
34
35
  __exportStar(require("./packages"), exports);
@@ -1,7 +1,15 @@
1
1
  import { z } from "zod";
2
2
  import { type IField } from "../../types/field-type";
3
+ /** Pure-data schema and storage metadata for a Peers table. */
3
4
  export interface ITableMetaData {
4
5
  tableId?: string;
6
+ /**
7
+ * Trusted owning package for package-scoped physical naming.
8
+ *
9
+ * When present, physical naming uses `name_packageId` while `tableId`
10
+ * remains the independent schema-definition identity.
11
+ */
12
+ packageId?: string;
5
13
  name: string;
6
14
  description: string;
7
15
  primaryKeyName: string;
@@ -17,5 +25,16 @@ export interface ITableMetaData {
17
25
  localOnly?: boolean;
18
26
  versionNumber?: number;
19
27
  }
28
+ /**
29
+ * Convert field metadata to the runtime Zod schema used by table validation.
30
+ *
31
+ * @param fields Serializable field definitions.
32
+ * @param codeSchema Optional author schema whose refinements are intersected.
33
+ */
20
34
  export declare function fieldsToSchema(fields: IField[], codeSchema?: z.AnyZodObject): z.AnyZodObject;
35
+ /**
36
+ * Convert a Zod object schema to serializable field metadata.
37
+ *
38
+ * @param schema Runtime record schema.
39
+ */
21
40
  export declare function schemaToFields(schema: z.AnyZodObject): IField[];
@@ -5,6 +5,12 @@ exports.schemaToFields = schemaToFields;
5
5
  const zod_1 = require("zod");
6
6
  const field_type_1 = require("../../types/field-type");
7
7
  const utils_1 = require("../../utils");
8
+ /**
9
+ * Convert field metadata to the runtime Zod schema used by table validation.
10
+ *
11
+ * @param fields Serializable field definitions.
12
+ * @param codeSchema Optional author schema whose refinements are intersected.
13
+ */
8
14
  function fieldsToSchema(fields, codeSchema) {
9
15
  let argsSchema = zod_1.z.object({});
10
16
  for (const argSchema of fields) {
@@ -52,6 +58,11 @@ function fieldsToSchema(fields, codeSchema) {
52
58
  }
53
59
  return argsSchema;
54
60
  }
61
+ /**
62
+ * Convert a Zod object schema to serializable field metadata.
63
+ *
64
+ * @param schema Runtime record schema.
65
+ */
55
66
  function schemaToFields(schema) {
56
67
  const fields = [];
57
68
  for (const name of Object.keys(schema.shape)) {
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Validate a guest-supplied isolated-table or field identifier before it can
3
+ * reach ORM-generated SQL.
4
+ *
5
+ * This intentionally accepts only simple SQLite identifiers. The package
6
+ * manifest validator and host gateway can share this helper so malformed
7
+ * names are rejected even when a declaration bypasses artifact loading.
8
+ *
9
+ * @param identifier Guest-supplied logical table or field name.
10
+ * @param label Description used in validation errors.
11
+ * @returns The unchanged identifier.
12
+ * @throws When the identifier is empty or contains SQL syntax.
13
+ */
14
+ export declare function validateIsolatedSqlIdentifier(identifier: string, label?: string): string;
15
+ /**
16
+ * Validate a package-local table name.
17
+ *
18
+ * @param logicalName Guest-visible table name.
19
+ * @returns The unchanged logical name.
20
+ * @throws When the name is not a SQL-safe isolated identifier.
21
+ */
22
+ export declare function validatePackageTableName(logicalName: string): string;
23
+ /**
24
+ * Derive the physical SQLite name for a package-owned table.
25
+ *
26
+ * Package ID is appended so authors only need unique logical names inside
27
+ * their own package.
28
+ *
29
+ * @param packageId Trusted owning package ID.
30
+ * @param logicalName Package-local table name.
31
+ * @returns The package-scoped physical table name.
32
+ * @throws When package or table identity is invalid.
33
+ */
34
+ export declare function packageScopedTableName(packageId: string, logicalName: string): string;
35
+ /**
36
+ * Derive the stable host-only ID used to key `TableDefinitions`.
37
+ *
38
+ * The ID is intentionally separate from the physical SQLite name so schema
39
+ * tracking keeps a valid peer ID without producing a triple-suffixed table.
40
+ *
41
+ * @param packageId Trusted owning package ID.
42
+ * @param logicalName Package-local table name.
43
+ * @returns A deterministic peer ID for the package-owned table definition.
44
+ * @throws When package or table identity is invalid.
45
+ */
46
+ export declare function packageScopedTableId(packageId: string, logicalName: string): string;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateIsolatedSqlIdentifier = validateIsolatedSqlIdentifier;
4
+ exports.validatePackageTableName = validatePackageTableName;
5
+ exports.packageScopedTableName = packageScopedTableName;
6
+ exports.packageScopedTableId = packageScopedTableId;
7
+ const utils_1 = require("../utils");
8
+ const ISOLATED_SQL_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
9
+ /**
10
+ * Validate a guest-supplied isolated-table or field identifier before it can
11
+ * reach ORM-generated SQL.
12
+ *
13
+ * This intentionally accepts only simple SQLite identifiers. The package
14
+ * manifest validator and host gateway can share this helper so malformed
15
+ * names are rejected even when a declaration bypasses artifact loading.
16
+ *
17
+ * @param identifier Guest-supplied logical table or field name.
18
+ * @param label Description used in validation errors.
19
+ * @returns The unchanged identifier.
20
+ * @throws When the identifier is empty or contains SQL syntax.
21
+ */
22
+ function validateIsolatedSqlIdentifier(identifier, label = "Isolated SQL identifier") {
23
+ if (typeof identifier !== "string" || identifier.trim() === "") {
24
+ throw new Error(`${label} must be a non-empty string`);
25
+ }
26
+ if (!ISOLATED_SQL_IDENTIFIER.test(identifier)) {
27
+ throw new Error(`${label} must start with a letter or underscore and contain only letters, numbers, and underscores`);
28
+ }
29
+ return identifier;
30
+ }
31
+ /**
32
+ * Validate a package-local table name.
33
+ *
34
+ * @param logicalName Guest-visible table name.
35
+ * @returns The unchanged logical name.
36
+ * @throws When the name is not a SQL-safe isolated identifier.
37
+ */
38
+ function validatePackageTableName(logicalName) {
39
+ return validateIsolatedSqlIdentifier(logicalName, "Package table name");
40
+ }
41
+ /**
42
+ * Derive the physical SQLite name for a package-owned table.
43
+ *
44
+ * Package ID is appended so authors only need unique logical names inside
45
+ * their own package.
46
+ *
47
+ * @param packageId Trusted owning package ID.
48
+ * @param logicalName Package-local table name.
49
+ * @returns The package-scoped physical table name.
50
+ * @throws When package or table identity is invalid.
51
+ */
52
+ function packageScopedTableName(packageId, logicalName) {
53
+ if (!(0, utils_1.isid)(packageId)) {
54
+ throw new Error(`Invalid package ID for table: ${packageId}`);
55
+ }
56
+ return `${validatePackageTableName(logicalName)}_${packageId}`;
57
+ }
58
+ /**
59
+ * Derive the stable host-only ID used to key `TableDefinitions`.
60
+ *
61
+ * The ID is intentionally separate from the physical SQLite name so schema
62
+ * tracking keeps a valid peer ID without producing a triple-suffixed table.
63
+ *
64
+ * @param packageId Trusted owning package ID.
65
+ * @param logicalName Package-local table name.
66
+ * @returns A deterministic peer ID for the package-owned table definition.
67
+ * @throws When package or table identity is invalid.
68
+ */
69
+ function packageScopedTableId(packageId, logicalName) {
70
+ return (0, utils_1.deterministicTableId)(packageScopedTableName(packageId, logicalName));
71
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("../utils");
4
+ const package_table_identity_1 = require("./package-table-identity");
5
+ describe("package-scoped table identity", () => {
6
+ test("appends trusted package identity to the logical table name", () => {
7
+ const packageId = (0, utils_1.newid)();
8
+ const tableId = (0, package_table_identity_1.packageScopedTableId)(packageId, "Settings");
9
+ const metaData = {
10
+ packageId,
11
+ tableId,
12
+ name: "Settings",
13
+ description: "Package settings",
14
+ primaryKeyName: "settingId",
15
+ fields: [],
16
+ };
17
+ expect((0, package_table_identity_1.packageScopedTableName)(packageId, "Settings")).toBe(`Settings_${packageId}`);
18
+ expect((0, utils_1.getFullTableName)(metaData)).toBe(`Settings_${packageId}`);
19
+ expect((0, utils_1.getFullTableName)(metaData)).not.toContain(tableId);
20
+ expect((0, utils_1.isid)(tableId)).toBe(true);
21
+ expect((0, utils_1.isDeterministicTableId)(tableId)).toBe(true);
22
+ });
23
+ test("keeps existing table naming unchanged", () => {
24
+ const tableId = (0, utils_1.newid)();
25
+ expect((0, utils_1.getFullTableName)({
26
+ tableId,
27
+ name: "Settings",
28
+ description: "Existing table",
29
+ primaryKeyName: "settingId",
30
+ fields: [],
31
+ })).toBe(`Settings_${tableId}`);
32
+ });
33
+ test("uses package and logical name for stable internal IDs", () => {
34
+ const packageId = (0, utils_1.newid)();
35
+ expect((0, package_table_identity_1.packageScopedTableId)(packageId, "One")).toBe((0, package_table_identity_1.packageScopedTableId)(packageId, "One"));
36
+ expect((0, package_table_identity_1.packageScopedTableId)(packageId, "One")).not.toBe((0, package_table_identity_1.packageScopedTableId)(packageId, "Two"));
37
+ expect((0, package_table_identity_1.packageScopedTableId)(packageId, "One")).not.toBe((0, package_table_identity_1.packageScopedTableId)((0, utils_1.newid)(), "One"));
38
+ });
39
+ test("rejects invalid package IDs and empty logical names", () => {
40
+ expect(() => (0, package_table_identity_1.packageScopedTableName)("not-an-id", "Settings")).toThrow(/package ID/);
41
+ expect(() => (0, package_table_identity_1.packageScopedTableName)((0, utils_1.newid)(), " ")).toThrow(/non-empty/);
42
+ expect(() => (0, package_table_identity_1.packageScopedTableName)((0, utils_1.newid)(), 'State"; DROP TABLE Users; --')).toThrow(/only letters, numbers, and underscores/);
43
+ expect((0, package_table_identity_1.validateIsolatedSqlIdentifier)("state_value", "field")).toBe("state_value");
44
+ expect(() => (0, package_table_identity_1.validateIsolatedSqlIdentifier)("state.value", "field")).toThrow(/field/);
45
+ });
46
+ });
@@ -18,6 +18,8 @@ import { type PersistentVar } from "./persistent-vars";
18
18
  export interface IDevicePackagePrefs {
19
19
  /** Which PV this device currently runs */
20
20
  activePackageVersionId?: string;
21
+ /** Newer selected PV temporarily bypassed because this host could not run it. */
22
+ compatibilityFallbackForPackageVersionId?: string;
21
23
  /** If true, never auto-update this package */
22
24
  pinned?: boolean;
23
25
  /** Auto-update range: patch | minor | latest */
@@ -98,3 +100,14 @@ export declare function resolveDevicePackageVersion(pkg: IPackage, dataContext:
98
100
  * to advance the group-level active version on upgrade.
99
101
  */
100
102
  export declare function hasDeviceFollowOverride(prefs: IDevicePackagePrefs | undefined): boolean;
103
+ /**
104
+ * Read the stored local checkout path for a package, if one is set.
105
+ *
106
+ * Used so isolated local-dev packages can load `dist/package.bundle.js` from
107
+ * disk even when startup resolution does not receive an explicit `localPath`.
108
+ *
109
+ * @param packageId - Package whose `packageLocalPath_*` device var to read.
110
+ * @param dataContext - Data context that owns the device var.
111
+ * @returns Trimmed path, or `undefined` when unset or empty.
112
+ */
113
+ export declare function readStoredPackageLocalPath(packageId: string, dataContext: DataContext): Promise<string | undefined>;