@prisma-idb/family-idb 0.0.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/LICENSE +21 -0
- package/README.md +30 -0
- package/dist/bin/prisma-idb.d.mts +1 -0
- package/dist/bin/prisma-idb.mjs +13 -0
- package/dist/bin/prisma-idb.mjs.map +1 -0
- package/dist/chain-order-CaHXo5jz.mjs +44 -0
- package/dist/chain-order-CaHXo5jz.mjs.map +1 -0
- package/dist/cli-Dlaym_DD.mjs +342 -0
- package/dist/cli-Dlaym_DD.mjs.map +1 -0
- package/dist/contract-space-codegen--gfuyap2.mjs +128 -0
- package/dist/contract-space-codegen--gfuyap2.mjs.map +1 -0
- package/dist/exports/cli.d.mts +26 -0
- package/dist/exports/cli.d.mts.map +1 -0
- package/dist/exports/cli.mjs +2 -0
- package/dist/exports/config-types.d.mts +38 -0
- package/dist/exports/config-types.d.mts.map +1 -0
- package/dist/exports/config-types.mjs +66 -0
- package/dist/exports/config-types.mjs.map +1 -0
- package/dist/exports/contract-psl.d.mts +59 -0
- package/dist/exports/contract-psl.d.mts.map +1 -0
- package/dist/exports/contract-psl.mjs +97 -0
- package/dist/exports/contract-psl.mjs.map +1 -0
- package/dist/exports/contract-ts.d.mts +98 -0
- package/dist/exports/contract-ts.d.mts.map +1 -0
- package/dist/exports/contract-ts.mjs +275 -0
- package/dist/exports/contract-ts.mjs.map +1 -0
- package/dist/exports/control.d.mts +107 -0
- package/dist/exports/control.d.mts.map +1 -0
- package/dist/exports/control.mjs +573 -0
- package/dist/exports/control.mjs.map +1 -0
- package/dist/exports/pack.d.mts +10 -0
- package/dist/exports/pack.d.mts.map +1 -0
- package/dist/exports/pack.mjs +11 -0
- package/dist/exports/pack.mjs.map +1 -0
- package/dist/migration-plan-BWhd8BdV.mjs +330 -0
- package/dist/migration-plan-BWhd8BdV.mjs.map +1 -0
- package/dist/preflight-4jDCNvS0.mjs +119 -0
- package/dist/preflight-4jDCNvS0.mjs.map +1 -0
- package/dist/psl-interpreter-C0suaXu2.d.mts +45 -0
- package/dist/psl-interpreter-C0suaXu2.d.mts.map +1 -0
- package/dist/psl-interpreter-C6Q4okuO.mjs +809 -0
- package/dist/psl-interpreter-C6Q4okuO.mjs.map +1 -0
- package/dist/validate-BLnItEKs.mjs +48 -0
- package/dist/validate-BLnItEKs.mjs.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { t as validateContract } from "../validate-BLnItEKs.mjs";
|
|
2
|
+
import { i as warnDroppedRelation, n as isValidIdbKeyCodec, r as literalValueMatchesCodec } from "../psl-interpreter-C6Q4okuO.mjs";
|
|
3
|
+
import { UNBOUND_DOMAIN_NAMESPACE_ID, crossRef } from "@prisma/orm-framework/contract/types";
|
|
4
|
+
import { computeProfileHash, computeStorageHash } from "@prisma/orm-framework/contract/hashing";
|
|
5
|
+
//#region src/core/contract-builder.ts
|
|
6
|
+
const SCALAR_TO_CODEC_ID = {
|
|
7
|
+
String: "idb/string@1",
|
|
8
|
+
Int: "idb/int32@1",
|
|
9
|
+
Float: "idb/double@1",
|
|
10
|
+
Boolean: "idb/bool@1",
|
|
11
|
+
DateTime: "idb/date@1",
|
|
12
|
+
BigInt: "idb/bigint@1",
|
|
13
|
+
Decimal: "idb/decimal@1",
|
|
14
|
+
Json: "idb/json@1",
|
|
15
|
+
Bytes: "idb/bytes@1"
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Drops `exclude: true` models and `excludeFields` entries for the client
|
|
19
|
+
* projection (ADR 012). Any relation a survivor still has to an excluded
|
|
20
|
+
* model is dropped too (ADR 013) — for every cardinality, required or not;
|
|
21
|
+
* the model itself is never excluded as a result (see ADR 013 §"Why we
|
|
22
|
+
* don't cascade on requiredness"). The underlying FK scalar field (if any)
|
|
23
|
+
* is kept, orphaned but inert.
|
|
24
|
+
*/
|
|
25
|
+
function projectModelsForClient(models) {
|
|
26
|
+
const excludedModelNames = new Set(Object.entries(models).filter(([, def]) => def.exclude === true).map(([name]) => name));
|
|
27
|
+
const result = {};
|
|
28
|
+
for (const [modelName, def] of Object.entries(models)) {
|
|
29
|
+
if (excludedModelNames.has(modelName)) continue;
|
|
30
|
+
const excludedFields = new Set(def.excludeFields ?? []);
|
|
31
|
+
if (excludedFields.has(def.key)) throw new Error(`defineContract: model "${modelName}" excludes its own key field "${def.key}" — the client contract needs a primary key for every included model.`);
|
|
32
|
+
for (const excludedField of excludedFields) {
|
|
33
|
+
if (excludedField === def.key) continue;
|
|
34
|
+
if (!(excludedField in def.fields)) throw new Error(`defineContract: model "${modelName}" excludeFields references unknown field "${excludedField}" — it is not declared in "fields".`);
|
|
35
|
+
}
|
|
36
|
+
for (const [indexName, idx] of Object.entries(def.indexes ?? {})) if (excludedFields.has(idx.keyPath)) throw new Error(`defineContract: model "${modelName}" index "${indexName}" references excluded field "${idx.keyPath}". Remove the index or the exclusion.`);
|
|
37
|
+
const relations = {};
|
|
38
|
+
for (const [relName, rel] of Object.entries(def.relations ?? {})) {
|
|
39
|
+
if (excludedModelNames.has(rel.to)) {
|
|
40
|
+
warnDroppedRelation(modelName, relName, rel.to);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const excludedLocalField = rel.on.local.find((f) => excludedFields.has(f));
|
|
44
|
+
if (excludedLocalField !== void 0) throw new Error(`defineContract: model "${modelName}" field "${excludedLocalField}" backs relation "${relName}" and cannot be excluded independently — field-level FK exclusion isn't supported (ADR 013 only handles relations pointing at a whole-model @@idb.exclude). Exclude the whole model instead, or remove the exclusion.`);
|
|
45
|
+
const targetExcludedFields = new Set(models[rel.to]?.excludeFields ?? []);
|
|
46
|
+
const excludedTargetField = rel.on.target.find((f) => targetExcludedFields.has(f));
|
|
47
|
+
if (excludedTargetField !== void 0) throw new Error(`defineContract: model "${modelName}" relation "${relName}" references excluded field "${rel.to}.${excludedTargetField}" — field-level FK exclusion isn't supported (ADR 013 only handles relations pointing at a whole-model @@idb.exclude). Exclude the whole model instead, or remove the exclusion.`);
|
|
48
|
+
relations[relName] = rel;
|
|
49
|
+
}
|
|
50
|
+
const fields = {};
|
|
51
|
+
for (const [fieldName, spec] of Object.entries(def.fields)) {
|
|
52
|
+
if (excludedFields.has(fieldName)) continue;
|
|
53
|
+
fields[fieldName] = spec;
|
|
54
|
+
}
|
|
55
|
+
result[modelName] = {
|
|
56
|
+
...def,
|
|
57
|
+
fields,
|
|
58
|
+
relations
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
function resolveFieldCodecId(def, fieldName) {
|
|
64
|
+
const spec = def.fields[fieldName];
|
|
65
|
+
if (spec === void 0) return void 0;
|
|
66
|
+
const typeName = spec.endsWith("?") ? spec.slice(0, -1) : spec;
|
|
67
|
+
return SCALAR_TO_CODEC_ID[typeName];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Mirrors `psl-interpreter.ts`'s `IDB_INVALID_KEY_TYPE`/`IDB_INVALID_INDEX_KEY_TYPE`
|
|
71
|
+
* diagnostics for the TS authoring surface, which has no diagnostics array —
|
|
72
|
+
* this DSL fails fast via `throw`, matching every other check in this file.
|
|
73
|
+
*
|
|
74
|
+
* `multiEntry` indexes are skipped: their key-validity depends on the
|
|
75
|
+
* *elements* of an array value, which this scalar-type system can't express
|
|
76
|
+
* (a `multiEntry` index only makes sense over a `Json`-typed field holding an
|
|
77
|
+
* array — the one case this file can't statically validate either way).
|
|
78
|
+
*/
|
|
79
|
+
function validateModelKeyAndIndexes(modelName, def) {
|
|
80
|
+
if (!(def.key in def.fields)) throw new Error(`defineContract: model "${modelName}" key field "${def.key}" is not declared in "fields".`);
|
|
81
|
+
if (def.fields[def.key]?.endsWith("?")) throw new Error(`defineContract: model "${modelName}" key field "${def.key}" is nullable ("${def.fields[def.key]}") — the primary key cannot be nullable.`);
|
|
82
|
+
const keyCodec = resolveFieldCodecId(def, def.key);
|
|
83
|
+
if (keyCodec !== void 0 && !isValidIdbKeyCodec(keyCodec)) throw new Error(`defineContract: model "${modelName}" key field "${def.key}" has type "${keyCodec}", which IndexedDB cannot use as a key. Every write would throw (DataError extracting the primary key). Use String, Int, Float, DateTime, Decimal, or Bytes instead.`);
|
|
84
|
+
for (const [indexName, idx] of Object.entries(def.indexes ?? {})) {
|
|
85
|
+
if (!(idx.keyPath in def.fields)) throw new Error(`defineContract: model "${modelName}" index "${indexName}" references field "${idx.keyPath}", which is not declared in "fields".`);
|
|
86
|
+
if (idx.multiEntry) continue;
|
|
87
|
+
const fieldCodec = resolveFieldCodecId(def, idx.keyPath);
|
|
88
|
+
if (fieldCodec === void 0 || isValidIdbKeyCodec(fieldCodec)) continue;
|
|
89
|
+
throw new Error(`defineContract: model "${modelName}" index "${indexName}" is keyed on "${idx.keyPath}" (type "${fieldCodec}"), which IndexedDB cannot use as an index key. Records are silently omitted from the index on write, and any query against it throws at runtime. Use String, Int, Float, DateTime, Decimal, or Bytes instead.`);
|
|
90
|
+
}
|
|
91
|
+
for (const [fieldName, value] of Object.entries(def.fieldDefaults ?? {})) {
|
|
92
|
+
if (!(fieldName in def.fields)) throw new Error(`defineContract: model "${modelName}" fieldDefaults references field "${fieldName}", which is not declared in "fields".`);
|
|
93
|
+
const codec = resolveFieldCodecId(def, fieldName);
|
|
94
|
+
if (codec !== void 0 && !literalValueMatchesCodec(value, codec)) throw new Error(`defineContract: model "${modelName}" fieldDefaults["${fieldName}"] is a ${typeof value} value, but field "${fieldName}" has type "${def.fields[fieldName]}" — the literal default's JS type must match the field's declared type.`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function sameFieldList(a, b) {
|
|
98
|
+
return a.length === b.length && a.every((field, index) => field === b[index]);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* `onDelete`/`onUpdate` may be declared on either side of a relation — unlike
|
|
102
|
+
* PSL (which only permits it on the FK-owning/`N:1` side), TS-DSL relation
|
|
103
|
+
* entries are self-contained and don't require a paired reciprocal
|
|
104
|
+
* declaration at all (see the `posts`-only test below). But if BOTH a
|
|
105
|
+
* relation and its reciprocal declare the *same* kind of action, only the
|
|
106
|
+
* side enforcement actually walks from is ever read at runtime
|
|
107
|
+
* (`getReferentialActionForRelation` in `client-idb/src/core/mutation-executor.ts`
|
|
108
|
+
* checks the direct side first and only falls back to the inverse when the
|
|
109
|
+
* direct side is silent) — so the other side's declaration would be silently
|
|
110
|
+
* ignored. That's a footgun, not a legitimate "pick one" scenario, so it's
|
|
111
|
+
* rejected here instead.
|
|
112
|
+
*/
|
|
113
|
+
function validateNoConflictingRelationActions(models) {
|
|
114
|
+
for (const [modelName, def] of Object.entries(models)) for (const [relName, rel] of Object.entries(def.relations ?? {})) {
|
|
115
|
+
const relatedDef = models[rel.to];
|
|
116
|
+
if (!relatedDef) continue;
|
|
117
|
+
for (const [inverseRelName, inverseRel] of Object.entries(relatedDef.relations ?? {})) {
|
|
118
|
+
if (inverseRel.to !== modelName) continue;
|
|
119
|
+
if (!sameFieldList(inverseRel.on.local, rel.on.target)) continue;
|
|
120
|
+
if (!sameFieldList(inverseRel.on.target, rel.on.local)) continue;
|
|
121
|
+
for (const kind of ["onDelete", "onUpdate"]) if (rel[kind] !== void 0 && inverseRel[kind] !== void 0) throw new Error(`defineContract: relation "${modelName}.${relName}" and its reciprocal "${rel.to}.${inverseRelName}" both declare "${kind}". Only one side is ever read at runtime (whichever side enforcement's cascade walk starts from — the "1:N" side, or the non-owning "1:1" side); the other declaration would be silently ignored. Declare "${kind}" on one side only.`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function buildFields(fields) {
|
|
126
|
+
const result = {};
|
|
127
|
+
for (const [name, spec] of Object.entries(fields)) {
|
|
128
|
+
const nullable = spec.endsWith("?");
|
|
129
|
+
const typeName = nullable ? spec.slice(0, -1) : spec;
|
|
130
|
+
const codecId = SCALAR_TO_CODEC_ID[typeName];
|
|
131
|
+
if (codecId === void 0) throw new Error(`Unknown field type "${typeName}" for field "${name}"`);
|
|
132
|
+
result[name] = {
|
|
133
|
+
nullable,
|
|
134
|
+
type: {
|
|
135
|
+
kind: "scalar",
|
|
136
|
+
codecId
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
function buildRoots(models) {
|
|
143
|
+
const roots = {};
|
|
144
|
+
for (const modelName of Object.keys(models)) {
|
|
145
|
+
const def = models[modelName];
|
|
146
|
+
roots[def.store] = crossRef(modelName);
|
|
147
|
+
}
|
|
148
|
+
return roots;
|
|
149
|
+
}
|
|
150
|
+
function buildStores(models) {
|
|
151
|
+
const stores = {};
|
|
152
|
+
for (const def of Object.values(models)) {
|
|
153
|
+
const indexes = {};
|
|
154
|
+
for (const [indexName, idx] of Object.entries(def.indexes ?? {})) indexes[indexName] = {
|
|
155
|
+
keyPath: idx.keyPath,
|
|
156
|
+
unique: idx.unique ?? false,
|
|
157
|
+
...idx.multiEntry !== void 0 ? { multiEntry: idx.multiEntry } : {}
|
|
158
|
+
};
|
|
159
|
+
stores[def.store] = {
|
|
160
|
+
keyPath: def.key,
|
|
161
|
+
...Object.keys(indexes).length > 0 ? { indexes } : {}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return stores;
|
|
165
|
+
}
|
|
166
|
+
function buildModels(models) {
|
|
167
|
+
const result = {};
|
|
168
|
+
for (const [modelName, def] of Object.entries(models)) {
|
|
169
|
+
const relations = {};
|
|
170
|
+
const relationsStorage = {};
|
|
171
|
+
for (const [relName, rel] of Object.entries(def.relations ?? {})) {
|
|
172
|
+
relations[relName] = {
|
|
173
|
+
to: crossRef(rel.to),
|
|
174
|
+
cardinality: rel.cardinality,
|
|
175
|
+
on: {
|
|
176
|
+
localFields: rel.on.local,
|
|
177
|
+
targetFields: rel.on.target
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
if (rel.onDelete !== void 0 || rel.onUpdate !== void 0) relationsStorage[relName] = {
|
|
181
|
+
...rel.onDelete !== void 0 ? { onDelete: rel.onDelete } : {},
|
|
182
|
+
...rel.onUpdate !== void 0 ? { onUpdate: rel.onUpdate } : {}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const storage = {
|
|
186
|
+
storeName: def.store,
|
|
187
|
+
keyPath: def.key,
|
|
188
|
+
...Object.keys(relationsStorage).length > 0 ? { relations: relationsStorage } : {},
|
|
189
|
+
...def.fieldDefaults && Object.keys(def.fieldDefaults).length > 0 ? { fieldDefaults: def.fieldDefaults } : {}
|
|
190
|
+
};
|
|
191
|
+
result[modelName] = {
|
|
192
|
+
fields: buildFields(def.fields),
|
|
193
|
+
relations,
|
|
194
|
+
storage
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Builds a typed IDB contract from a developer-friendly model definition.
|
|
201
|
+
*
|
|
202
|
+
* This is the TypeScript-first (no-emit) authoring path per ADR 006. The
|
|
203
|
+
* returned contract object can be passed directly to `createIdbClient()` or
|
|
204
|
+
* to `typescriptContract()` for config-file usage.
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* import { defineContract } from '@prisma-idb/family-idb/contract-ts';
|
|
209
|
+
* import idbFamily from '@prisma-idb/family-idb/pack';
|
|
210
|
+
* import idbTarget from '@prisma-idb/target-idb/pack';
|
|
211
|
+
*
|
|
212
|
+
* export default defineContract({
|
|
213
|
+
* family: idbFamily,
|
|
214
|
+
* target: idbTarget,
|
|
215
|
+
* models: {
|
|
216
|
+
* User: {
|
|
217
|
+
* store: 'users',
|
|
218
|
+
* key: 'id',
|
|
219
|
+
* fields: { id: 'String', name: 'String?', email: 'String' },
|
|
220
|
+
* indexes: { byEmail: { keyPath: 'email', unique: true } },
|
|
221
|
+
* },
|
|
222
|
+
* },
|
|
223
|
+
* });
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
function defineContract(input, options) {
|
|
227
|
+
const models = (options?.projection ?? "full") === "client" ? projectModelsForClient(input.models) : input.models;
|
|
228
|
+
for (const [modelName, def] of Object.entries(models)) validateModelKeyAndIndexes(modelName, def);
|
|
229
|
+
validateNoConflictingRelationActions(models);
|
|
230
|
+
const stores = buildStores(models);
|
|
231
|
+
const capabilities = { idb: {
|
|
232
|
+
ddlOnlyInUpgrade: true,
|
|
233
|
+
transactionalDDL: true
|
|
234
|
+
} };
|
|
235
|
+
const ns = UNBOUND_DOMAIN_NAMESPACE_ID;
|
|
236
|
+
const storageBlock = {
|
|
237
|
+
stores,
|
|
238
|
+
namespaces: { [ns]: {
|
|
239
|
+
id: ns,
|
|
240
|
+
entries: {}
|
|
241
|
+
} }
|
|
242
|
+
};
|
|
243
|
+
const storageHash = computeStorageHash({
|
|
244
|
+
target: "idb",
|
|
245
|
+
targetFamily: "idb",
|
|
246
|
+
storage: storageBlock
|
|
247
|
+
});
|
|
248
|
+
const profileHash = computeProfileHash({
|
|
249
|
+
target: "idb",
|
|
250
|
+
targetFamily: "idb",
|
|
251
|
+
capabilities
|
|
252
|
+
});
|
|
253
|
+
const storage = {
|
|
254
|
+
...storageBlock,
|
|
255
|
+
storageHash
|
|
256
|
+
};
|
|
257
|
+
const domain = { namespaces: { [ns]: { models: buildModels(models) } } };
|
|
258
|
+
const contract = {
|
|
259
|
+
target: "idb",
|
|
260
|
+
targetFamily: "idb",
|
|
261
|
+
roots: buildRoots(models),
|
|
262
|
+
domain,
|
|
263
|
+
storage,
|
|
264
|
+
capabilities,
|
|
265
|
+
extensions: {},
|
|
266
|
+
meta: {},
|
|
267
|
+
profileHash
|
|
268
|
+
};
|
|
269
|
+
validateContract(contract);
|
|
270
|
+
return contract;
|
|
271
|
+
}
|
|
272
|
+
//#endregion
|
|
273
|
+
export { defineContract };
|
|
274
|
+
|
|
275
|
+
//# sourceMappingURL=contract-ts.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-ts.mjs","names":[],"sources":["../../src/core/contract-builder.ts"],"sourcesContent":["import { computeProfileHash, computeStorageHash } from \"@prisma/orm-framework/contract/hashing\";\nimport type { ApplicationDomain, Contract, ContractField, CrossReference } from \"@prisma/orm-framework/contract/types\";\nimport { UNBOUND_DOMAIN_NAMESPACE_ID, crossRef } from \"@prisma/orm-framework/contract/types\";\nimport type {\n IdbIndexDefinition,\n IdbModelStorage,\n IdbReferentialAction,\n IdbStorage,\n IdbStoreDefinition,\n} from \"@prisma-idb/target-idb/pack\";\nimport type { ContractProjection } from \"./psl-interpreter\";\nimport { isValidIdbKeyCodec, literalValueMatchesCodec, warnDroppedRelation } from \"./psl-interpreter\";\nimport { validateContract } from \"./validate\";\n\n// ── Field type system ─────────────────────────────────────────────────────────\n\ntype PrismaScalarType = \"String\" | \"Int\" | \"Float\" | \"Boolean\" | \"DateTime\" | \"BigInt\" | \"Decimal\" | \"Json\" | \"Bytes\";\n\n/**\n * A field spec string: the Prisma scalar type name, optionally suffixed with\n * `?` to indicate the field is nullable (e.g. `\"String\"`, `\"Int?\"`, `\"DateTime?\"`).\n */\nexport type FieldSpec = PrismaScalarType | `${PrismaScalarType}?`;\n\nconst SCALAR_TO_CODEC_ID: Record<PrismaScalarType, string> = {\n String: \"idb/string@1\",\n Int: \"idb/int32@1\",\n Float: \"idb/double@1\",\n Boolean: \"idb/bool@1\",\n DateTime: \"idb/date@1\",\n BigInt: \"idb/bigint@1\",\n Decimal: \"idb/decimal@1\",\n Json: \"idb/json@1\",\n Bytes: \"idb/bytes@1\",\n};\n\n// ── Input types ───────────────────────────────────────────────────────────────\n\nexport type RelationDef = {\n readonly to: string;\n readonly cardinality: \"1:1\" | \"1:N\" | \"N:1\";\n readonly on: {\n readonly local: readonly string[];\n readonly target: readonly string[];\n };\n readonly onDelete?: IdbReferentialAction;\n readonly onUpdate?: IdbReferentialAction;\n};\n\nexport type IndexDef = {\n readonly keyPath: string;\n readonly unique?: boolean;\n readonly multiEntry?: boolean;\n};\n\nexport type ModelDef = {\n readonly store: string;\n readonly key: string;\n /** All scalar fields on the model. Use `\"Type\"` for non-nullable, `\"Type?\"` for nullable. */\n readonly fields: Record<string, FieldSpec>;\n readonly indexes?: Record<string, IndexDef>;\n readonly relations?: Record<string, RelationDef>;\n /**\n * Literal default values, keyed by field name — feeds the `setDefault`\n * referential action (a child's FK field is reset to its own declared\n * default when its parent is deleted/updated with `onDelete`/`onUpdate:\n * \"setDefault\"`). A sibling map to `fields`, deliberately not folded into\n * `FieldSpec` (a bare type string) to avoid widening that type.\n *\n * Does NOT feed `create()`'s default-filling — the TS-DSL authoring path\n * has no `execution.mutations.defaults` support at all yet (a separate,\n * larger, pre-existing gap). Use the PSL authoring path's `@default(...)`\n * if you need create-time defaults; this map only backs `setDefault`.\n */\n readonly fieldDefaults?: Record<string, string | number | boolean>;\n /** Server-only model — dropped when `defineContract` runs with `{ projection: \"client\" }`. See ADR 012. */\n readonly exclude?: boolean;\n /** Server-only fields on an otherwise-synced model — dropped in client projection. See ADR 012. */\n readonly excludeFields?: readonly string[];\n};\n\nexport type DefineContractInput = {\n /** Pass the default export of `@prisma-idb/family-idb/pack`. */\n readonly family: { readonly familyId: \"idb\"; readonly id: string };\n /** Pass the default export of `@prisma-idb/target-idb/pack`. */\n readonly target: { readonly targetId: string; readonly id: string };\n readonly models: Record<string, ModelDef>;\n};\n\nexport type DefineContractOptions = {\n /** @default \"full\" */\n readonly projection?: ContractProjection;\n};\n\n/**\n * Drops `exclude: true` models and `excludeFields` entries for the client\n * projection (ADR 012). Any relation a survivor still has to an excluded\n * model is dropped too (ADR 013) — for every cardinality, required or not;\n * the model itself is never excluded as a result (see ADR 013 §\"Why we\n * don't cascade on requiredness\"). The underlying FK scalar field (if any)\n * is kept, orphaned but inert.\n */\nfunction projectModelsForClient(models: Record<string, ModelDef>): Record<string, ModelDef> {\n const excludedModelNames = new Set(\n Object.entries(models)\n .filter(([, def]) => def.exclude === true)\n .map(([name]) => name)\n );\n\n const result: Record<string, ModelDef> = {};\n\n for (const [modelName, def] of Object.entries(models)) {\n if (excludedModelNames.has(modelName)) continue;\n\n const excludedFields = new Set(def.excludeFields ?? []);\n\n if (excludedFields.has(def.key)) {\n throw new Error(\n `defineContract: model \"${modelName}\" excludes its own key field \"${def.key}\" — the client contract needs a primary key for every included model.`\n );\n }\n\n for (const excludedField of excludedFields) {\n if (excludedField === def.key) continue;\n if (!(excludedField in def.fields)) {\n throw new Error(\n `defineContract: model \"${modelName}\" excludeFields references unknown field \"${excludedField}\" — it is not declared in \"fields\".`\n );\n }\n }\n\n for (const [indexName, idx] of Object.entries(def.indexes ?? {})) {\n if (excludedFields.has(idx.keyPath)) {\n throw new Error(\n `defineContract: model \"${modelName}\" index \"${indexName}\" references excluded field \"${idx.keyPath}\". Remove the index or the exclusion.`\n );\n }\n }\n\n const relations: Record<string, RelationDef> = {};\n for (const [relName, rel] of Object.entries(def.relations ?? {})) {\n if (excludedModelNames.has(rel.to)) {\n warnDroppedRelation(modelName, relName, rel.to);\n continue;\n }\n const excludedLocalField = rel.on.local.find((f) => excludedFields.has(f));\n if (excludedLocalField !== undefined) {\n throw new Error(\n `defineContract: model \"${modelName}\" field \"${excludedLocalField}\" backs relation \"${relName}\" and cannot be excluded independently — field-level FK exclusion isn't supported (ADR 013 only handles relations pointing at a whole-model @@idb.exclude). Exclude the whole model instead, or remove the exclusion.`\n );\n }\n const targetExcludedFields = new Set(models[rel.to]?.excludeFields ?? []);\n const excludedTargetField = rel.on.target.find((f) => targetExcludedFields.has(f));\n if (excludedTargetField !== undefined) {\n throw new Error(\n `defineContract: model \"${modelName}\" relation \"${relName}\" references excluded field \"${rel.to}.${excludedTargetField}\" — field-level FK exclusion isn't supported (ADR 013 only handles relations pointing at a whole-model @@idb.exclude). Exclude the whole model instead, or remove the exclusion.`\n );\n }\n relations[relName] = rel;\n }\n\n const fields: Record<string, FieldSpec> = {};\n for (const [fieldName, spec] of Object.entries(def.fields)) {\n if (excludedFields.has(fieldName)) continue;\n fields[fieldName] = spec;\n }\n\n result[modelName] = { ...def, fields, relations };\n }\n\n return result;\n}\n\n// ── Validate key/index field types against IDB's valid-key algorithm ──────────\n\nfunction resolveFieldCodecId(def: ModelDef, fieldName: string): string | undefined {\n const spec = def.fields[fieldName];\n if (spec === undefined) return undefined;\n const typeName = (spec.endsWith(\"?\") ? spec.slice(0, -1) : spec) as PrismaScalarType;\n return SCALAR_TO_CODEC_ID[typeName];\n}\n\n/**\n * Mirrors `psl-interpreter.ts`'s `IDB_INVALID_KEY_TYPE`/`IDB_INVALID_INDEX_KEY_TYPE`\n * diagnostics for the TS authoring surface, which has no diagnostics array —\n * this DSL fails fast via `throw`, matching every other check in this file.\n *\n * `multiEntry` indexes are skipped: their key-validity depends on the\n * *elements* of an array value, which this scalar-type system can't express\n * (a `multiEntry` index only makes sense over a `Json`-typed field holding an\n * array — the one case this file can't statically validate either way).\n */\nfunction validateModelKeyAndIndexes(modelName: string, def: ModelDef): void {\n if (!(def.key in def.fields)) {\n throw new Error(`defineContract: model \"${modelName}\" key field \"${def.key}\" is not declared in \"fields\".`);\n }\n if (def.fields[def.key]?.endsWith(\"?\")) {\n throw new Error(\n `defineContract: model \"${modelName}\" key field \"${def.key}\" is nullable (\"${def.fields[def.key]}\") — the primary key cannot be nullable.`\n );\n }\n const keyCodec = resolveFieldCodecId(def, def.key);\n if (keyCodec !== undefined && !isValidIdbKeyCodec(keyCodec)) {\n throw new Error(\n `defineContract: model \"${modelName}\" key field \"${def.key}\" has type \"${keyCodec}\", which IndexedDB cannot use as a key. Every write would throw (DataError extracting the primary key). Use String, Int, Float, DateTime, Decimal, or Bytes instead.`\n );\n }\n\n for (const [indexName, idx] of Object.entries(def.indexes ?? {})) {\n if (!(idx.keyPath in def.fields)) {\n throw new Error(\n `defineContract: model \"${modelName}\" index \"${indexName}\" references field \"${idx.keyPath}\", which is not declared in \"fields\".`\n );\n }\n if (idx.multiEntry) continue;\n const fieldCodec = resolveFieldCodecId(def, idx.keyPath);\n if (fieldCodec === undefined || isValidIdbKeyCodec(fieldCodec)) continue;\n throw new Error(\n `defineContract: model \"${modelName}\" index \"${indexName}\" is keyed on \"${idx.keyPath}\" (type \"${fieldCodec}\"), which IndexedDB cannot use as an index key. Records are silently omitted from the index on write, and any query against it throws at runtime. Use String, Int, Float, DateTime, Decimal, or Bytes instead.`\n );\n }\n\n for (const [fieldName, value] of Object.entries(def.fieldDefaults ?? {})) {\n if (!(fieldName in def.fields)) {\n throw new Error(\n `defineContract: model \"${modelName}\" fieldDefaults references field \"${fieldName}\", which is not declared in \"fields\".`\n );\n }\n // Mirrors PSL's `literalValueMatchesCodec` check on `@default(...)` — a\n // literal default's JS type must match the field's declared type, and\n // only String/Int/Float/Decimal/Boolean fields accept a literal default\n // at all (matching `ModelDef.fieldDefaults`'s own `string | number |\n // boolean` value type).\n const codec = resolveFieldCodecId(def, fieldName);\n if (codec !== undefined && !literalValueMatchesCodec(value, codec)) {\n throw new Error(\n `defineContract: model \"${modelName}\" fieldDefaults[\"${fieldName}\"] is a ${typeof value} value, but field \"${fieldName}\" has type \"${def.fields[fieldName]}\" — the literal default's JS type must match the field's declared type.`\n );\n }\n }\n}\n\n// ── Validate no conflicting reciprocal relation-action declarations ───────────\n\nfunction sameFieldList(a: readonly string[], b: readonly string[]): boolean {\n return a.length === b.length && a.every((field, index) => field === b[index]);\n}\n\n/**\n * `onDelete`/`onUpdate` may be declared on either side of a relation — unlike\n * PSL (which only permits it on the FK-owning/`N:1` side), TS-DSL relation\n * entries are self-contained and don't require a paired reciprocal\n * declaration at all (see the `posts`-only test below). But if BOTH a\n * relation and its reciprocal declare the *same* kind of action, only the\n * side enforcement actually walks from is ever read at runtime\n * (`getReferentialActionForRelation` in `client-idb/src/core/mutation-executor.ts`\n * checks the direct side first and only falls back to the inverse when the\n * direct side is silent) — so the other side's declaration would be silently\n * ignored. That's a footgun, not a legitimate \"pick one\" scenario, so it's\n * rejected here instead.\n */\nfunction validateNoConflictingRelationActions(models: Record<string, ModelDef>): void {\n for (const [modelName, def] of Object.entries(models)) {\n for (const [relName, rel] of Object.entries(def.relations ?? {})) {\n const relatedDef = models[rel.to];\n if (!relatedDef) continue;\n for (const [inverseRelName, inverseRel] of Object.entries(relatedDef.relations ?? {})) {\n if (inverseRel.to !== modelName) continue;\n // Mirrors the reciprocal-match predicate in `getReferentialActionForRelation`\n // (client-idb/src/core/mutation-executor.ts) — keep these in sync. Matching on\n // fields (not just `to`) is required: two distinct relations to the same model\n // (e.g. `Message.sender` / `Message.recipient` both -> `User`) are not reciprocals\n // of each other and must not be flagged.\n if (!sameFieldList(inverseRel.on.local, rel.on.target)) continue;\n if (!sameFieldList(inverseRel.on.target, rel.on.local)) continue;\n\n for (const kind of [\"onDelete\", \"onUpdate\"] as const) {\n if (rel[kind] !== undefined && inverseRel[kind] !== undefined) {\n throw new Error(\n `defineContract: relation \"${modelName}.${relName}\" and its reciprocal \"${rel.to}.${inverseRelName}\" ` +\n `both declare \"${kind}\". Only one side is ever read at runtime (whichever side enforcement's cascade ` +\n `walk starts from — the \"1:N\" side, or the non-owning \"1:1\" side); the other declaration would be ` +\n `silently ignored. Declare \"${kind}\" on one side only.`\n );\n }\n }\n }\n }\n }\n}\n\n// ── Helper: build ContractField entries from field specs ──────────────────────\n\nfunction buildFields(fields: Record<string, FieldSpec>): Record<string, ContractField> {\n const result: Record<string, ContractField> = {};\n for (const [name, spec] of Object.entries(fields)) {\n const nullable = spec.endsWith(\"?\");\n const typeName = (nullable ? spec.slice(0, -1) : spec) as PrismaScalarType;\n const codecId = SCALAR_TO_CODEC_ID[typeName];\n if (codecId === undefined) {\n throw new Error(`Unknown field type \"${typeName}\" for field \"${name}\"`);\n }\n result[name] = { nullable, type: { kind: \"scalar\" as const, codecId } };\n }\n return result;\n}\n\n// ── Helper: derive the `roots` map (storeName → model CrossReference) ─────────\n\nfunction buildRoots(models: Record<string, ModelDef>): Record<string, CrossReference> {\n const roots: Record<string, CrossReference> = {};\n for (const modelName of Object.keys(models)) {\n const def = models[modelName]!;\n // v0.12.0: roots values are CrossReference `{ namespace, model }`, not bare\n // model-name strings. IDB has a single (unbound) namespace.\n roots[def.store] = crossRef(modelName);\n }\n return roots;\n}\n\n// ── Helper: derive storage.stores from model definitions ─────────────────────\n\nfunction buildStores(models: Record<string, ModelDef>): Record<string, IdbStoreDefinition> {\n const stores: Record<string, IdbStoreDefinition> = {};\n for (const def of Object.values(models)) {\n const indexes: Record<string, IdbIndexDefinition> = {};\n for (const [indexName, idx] of Object.entries(def.indexes ?? {})) {\n indexes[indexName] = {\n keyPath: idx.keyPath,\n unique: idx.unique ?? false,\n ...(idx.multiEntry !== undefined ? { multiEntry: idx.multiEntry } : {}),\n };\n }\n stores[def.store] = {\n keyPath: def.key,\n ...(Object.keys(indexes).length > 0 ? { indexes } : {}),\n };\n }\n return stores;\n}\n\n// ── Helper: build the models section ─────────────────────────────────────────\n\ntype ContractModelEntry = {\n readonly fields: Record<string, ContractField>;\n readonly relations: Record<\n string,\n {\n readonly to: CrossReference;\n readonly cardinality: \"1:1\" | \"1:N\" | \"N:1\";\n readonly on: { readonly localFields: readonly string[]; readonly targetFields: readonly string[] };\n }\n >;\n readonly storage: IdbModelStorage;\n};\n\nfunction buildModels(models: Record<string, ModelDef>): Record<string, ContractModelEntry> {\n const result: Record<string, ContractModelEntry> = {};\n for (const [modelName, def] of Object.entries(models)) {\n const relations: ContractModelEntry[\"relations\"] = {};\n const relationsStorage: Record<string, { onDelete?: IdbReferentialAction; onUpdate?: IdbReferentialAction }> = {};\n for (const [relName, rel] of Object.entries(def.relations ?? {})) {\n relations[relName] = {\n // v0.12.0: relation `to` is a CrossReference, not a bare model-name string.\n to: crossRef(rel.to),\n cardinality: rel.cardinality,\n on: { localFields: rel.on.local, targetFields: rel.on.target },\n };\n if (rel.onDelete !== undefined || rel.onUpdate !== undefined) {\n relationsStorage[relName] = {\n ...(rel.onDelete !== undefined ? { onDelete: rel.onDelete } : {}),\n ...(rel.onUpdate !== undefined ? { onUpdate: rel.onUpdate } : {}),\n };\n }\n }\n const storage: IdbModelStorage = {\n storeName: def.store,\n keyPath: def.key,\n ...(Object.keys(relationsStorage).length > 0 ? { relations: relationsStorage } : {}),\n ...(def.fieldDefaults && Object.keys(def.fieldDefaults).length > 0 ? { fieldDefaults: def.fieldDefaults } : {}),\n };\n result[modelName] = { fields: buildFields(def.fields), relations, storage };\n }\n return result;\n}\n\n// ── defineContract ────────────────────────────────────────────────────────────\n\n/**\n * Builds a typed IDB contract from a developer-friendly model definition.\n *\n * This is the TypeScript-first (no-emit) authoring path per ADR 006. The\n * returned contract object can be passed directly to `createIdbClient()` or\n * to `typescriptContract()` for config-file usage.\n *\n * @example\n * ```ts\n * import { defineContract } from '@prisma-idb/family-idb/contract-ts';\n * import idbFamily from '@prisma-idb/family-idb/pack';\n * import idbTarget from '@prisma-idb/target-idb/pack';\n *\n * export default defineContract({\n * family: idbFamily,\n * target: idbTarget,\n * models: {\n * User: {\n * store: 'users',\n * key: 'id',\n * fields: { id: 'String', name: 'String?', email: 'String' },\n * indexes: { byEmail: { keyPath: 'email', unique: true } },\n * },\n * },\n * });\n * ```\n */\nexport function defineContract(input: DefineContractInput, options?: DefineContractOptions): Contract<IdbStorage> {\n const projection: ContractProjection = options?.projection ?? \"full\";\n const models = projection === \"client\" ? projectModelsForClient(input.models) : input.models;\n\n for (const [modelName, def] of Object.entries(models)) {\n validateModelKeyAndIndexes(modelName, def);\n }\n validateNoConflictingRelationActions(models);\n\n const stores = buildStores(models);\n\n // Mirror the capability surface that `prisma contract emit` writes\n // into the JSON contract — keeps the two authoring paths byte-equivalent\n // for the capabilities block. See ARCHITECTURE.md § \"Key type: capabilities\".\n const capabilities = {\n idb: {\n ddlOnlyInUpgrade: true,\n transactionalDDL: true,\n },\n };\n\n // v0.12.0 (ADR 221): both planes are namespace-keyed. IDB has no namespace\n // concept, so everything lives under the single unbound namespace.\n const ns = UNBOUND_DOMAIN_NAMESPACE_ID;\n const storageBlock = {\n stores,\n namespaces: { [ns]: { id: ns, entries: {} } },\n };\n\n const storageHash = computeStorageHash({\n target: \"idb\",\n targetFamily: \"idb\",\n storage: storageBlock,\n });\n\n const profileHash = computeProfileHash({\n target: \"idb\",\n targetFamily: \"idb\",\n capabilities,\n });\n\n const storage: IdbStorage = { ...storageBlock, storageHash };\n\n // v0.12.0: models live under `domain.namespaces.<ns>.models`, not a top-level\n // `models` field.\n const domain = {\n namespaces: { [ns]: { models: buildModels(models) } },\n } as unknown as ApplicationDomain;\n\n const contract: Contract<IdbStorage> = {\n target: \"idb\",\n targetFamily: \"idb\",\n roots: buildRoots(models),\n domain,\n storage,\n capabilities,\n extensions: {},\n meta: {},\n profileHash,\n };\n\n validateContract(contract);\n\n return contract;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { ContractSpace, ControlFamilyDescriptor, ControlFamilyInstance, ControlStack } from "@prisma/orm-framework/components/control";
|
|
2
|
+
import { Contract } from "@prisma/orm-framework/contract/types";
|
|
3
|
+
import { IdbStorage } from "@prisma-idb/target-idb/pack";
|
|
4
|
+
//#region src/core/schema-ir.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* IndexedDB schema IR — the in-memory representation of the object store
|
|
7
|
+
* structure used by introspect(), schemaVerify(), and the manifest format.
|
|
8
|
+
*
|
|
9
|
+
* Mirrors the contract types in `@prisma-idb/target-idb/pack` but
|
|
10
|
+
* is independent of any versioned contract hash so it can be used as a
|
|
11
|
+
* stable internal representation.
|
|
12
|
+
*/
|
|
13
|
+
type IdbIndexIR = {
|
|
14
|
+
readonly keyPath: string;
|
|
15
|
+
readonly unique: boolean;
|
|
16
|
+
readonly multiEntry?: boolean;
|
|
17
|
+
};
|
|
18
|
+
type IdbStoreIR = {
|
|
19
|
+
readonly keyPath: string;
|
|
20
|
+
readonly autoIncrement?: boolean;
|
|
21
|
+
readonly indexes?: Record<string, IdbIndexIR>;
|
|
22
|
+
};
|
|
23
|
+
type IdbSchemaIR = {
|
|
24
|
+
readonly stores: Record<string, IdbStoreIR>;
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/core/control-instance.d.ts
|
|
28
|
+
/** Fully-typed IDB control family instance returned by {@link createIdbFamilyInstance}. */
|
|
29
|
+
type IdbControlFamilyInstance = ControlFamilyInstance<"idb", IdbSchemaIR>;
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/core/control-descriptor.d.ts
|
|
32
|
+
/**
|
|
33
|
+
* IDB family descriptor — the control-plane entry point for the IDB family.
|
|
34
|
+
*
|
|
35
|
+
* Registered in a Prisma 8 config file under the `family` key:
|
|
36
|
+
* ```ts
|
|
37
|
+
* import idb from '@prisma-idb/family-idb/control';
|
|
38
|
+
* export default defineConfig({ family: idb, ... });
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* **Responsibilities:**
|
|
42
|
+
* - Carries the {@link idbEmission} plugin used by `prisma contract emit`
|
|
43
|
+
* to generate `contract.d.ts`.
|
|
44
|
+
* - Acts as a factory: `create(stack)` returns an {@link IdbControlFamilyInstance}
|
|
45
|
+
* with domain-action methods consumed by the CLI.
|
|
46
|
+
*/
|
|
47
|
+
declare class IdbFamilyDescriptor implements ControlFamilyDescriptor<"idb", IdbControlFamilyInstance> {
|
|
48
|
+
readonly kind: "family";
|
|
49
|
+
readonly id = "idb";
|
|
50
|
+
readonly familyId: "idb";
|
|
51
|
+
readonly version = "0.0.1";
|
|
52
|
+
readonly emission: {
|
|
53
|
+
readonly id: "idb";
|
|
54
|
+
readonly generateStorageType: (contract: import("@prisma/orm-framework/contract/types").Contract, storageHashTypeName: string) => string;
|
|
55
|
+
readonly generateModelStorageType: (_modelName: string, model: import("@prisma/orm-framework/contract/types").ContractModel) => string;
|
|
56
|
+
readonly getFamilyImports: () => string[];
|
|
57
|
+
readonly getFamilyTypeAliases: () => string;
|
|
58
|
+
readonly getTypeMapsExpression: () => string;
|
|
59
|
+
readonly getContractWrapper: (contractBaseName: string, typeMapsName: string) => string;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Creates an {@link IdbControlFamilyInstance} for the given control stack.
|
|
63
|
+
*
|
|
64
|
+
* The instance is a plain object whose methods implement the CLI domain
|
|
65
|
+
* actions (validateContract, verify, sign, etc.).
|
|
66
|
+
*/
|
|
67
|
+
create(stack: ControlStack<"idb", string>): IdbControlFamilyInstance;
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/core/extension-space.d.ts
|
|
71
|
+
/**
|
|
72
|
+
* A named contract space contributed by an IDB extension (e.g. the sync
|
|
73
|
+
* extension). Passed to {@link createAutoMigratingIdbClient} so the browser
|
|
74
|
+
* migration runner can walk each extension's migration graph independently of
|
|
75
|
+
* the application's `'app'` space.
|
|
76
|
+
*
|
|
77
|
+
* Each space writes its own row to the `_prisma_next_marker` store, keyed by
|
|
78
|
+
* `spaceId`, so extension migrations and application migrations version
|
|
79
|
+
* independently.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* import { idbSyncExtension } from '@prisma-idb/sync-extension-idb/control';
|
|
84
|
+
*
|
|
85
|
+
* const db = await createAutoMigratingIdbClient({
|
|
86
|
+
* contractSpace,
|
|
87
|
+
* dbName: 'my-app',
|
|
88
|
+
* extensions: [idbSyncExtension],
|
|
89
|
+
* });
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
interface IdbExtensionSpace {
|
|
93
|
+
/** Unique identifier for this contract space — e.g. `'idb-sync'`. */
|
|
94
|
+
readonly spaceId: string;
|
|
95
|
+
/** The extension's bundled contract + migration graph. */
|
|
96
|
+
readonly contractSpace: ContractSpace;
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/core/validate.d.ts
|
|
100
|
+
/** Fully-typed IDB contract after validation. */
|
|
101
|
+
type IdbContract = Contract<IdbStorage>;
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/exports/control.d.ts
|
|
104
|
+
declare const _default: IdbFamilyDescriptor;
|
|
105
|
+
//#endregion
|
|
106
|
+
export { type IdbContract, type IdbControlFamilyInstance, type IdbExtensionSpace, type IdbIndexIR, type IdbSchemaIR, type IdbStoreIR, _default as default };
|
|
107
|
+
//# sourceMappingURL=control.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"control.d.mts","names":[],"sources":["../../src/core/schema-ir.ts","../../src/core/control-instance.ts","../../src/core/control-descriptor.ts","../../src/core/extension-space.ts","../../src/core/validate.ts","../../src/exports/control.ts"],"mappings":";;;;;;;;;;;;KASY;WACD;WACA;WACA;;KAGC;WACD;WACA;WACA,UAAU,eAAe;;KAGxB;WACD,QAAQ,eAAe;;;;;KCmCtB,2BAA2B,6BAA6B;;;;;;;;;;;;;;;;;;cCtCvD,+BAA+B,+BAA+B;WAChE;WACA;WACA;WACA;WACA;;;;;;;;;;;;;;;EAQT,OAAO,OAAO,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;UCT7B;;WAEN;;WAEA,eAAe;;;;;KCpBd,cAAc,SAAS"}
|