@lunora/server 0.0.0 → 1.0.0-alpha.10
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.md +105 -0
- package/README.md +134 -9
- package/__assets__/package-og.svg +14 -0
- package/dist/data-model.d.mts +416 -0
- package/dist/data-model.d.ts +416 -0
- package/dist/data-model.mjs +1 -0
- package/dist/drizzle.d.mts +1 -0
- package/dist/drizzle.d.ts +1 -0
- package/dist/drizzle.mjs +1 -0
- package/dist/index.d.mts +1985 -0
- package/dist/index.d.ts +1985 -0
- package/dist/index.mjs +28 -0
- package/dist/packem_shared/LunoraEnvError-DjFkpkSP.mjs +187 -0
- package/dist/packem_shared/LunoraError-DN7Zhhvu.mjs +54 -0
- package/dist/packem_shared/PRESENCE_DEFAULT_TTL_MS-D8viLY1S.mjs +114 -0
- package/dist/packem_shared/asBucketStorage-Cnxd9y2q.mjs +11 -0
- package/dist/packem_shared/bindOrm-Ce57S3N9.mjs +128 -0
- package/dist/packem_shared/buildRlsReadRegistry-1jexWrb3.mjs +107 -0
- package/dist/packem_shared/composePluginMiddleware-Ck5_TUO8.mjs +100 -0
- package/dist/packem_shared/createPolicyDsl-De67zPDS.mjs +29 -0
- package/dist/packem_shared/createSecrets-TsIP9lOa.mjs +55 -0
- package/dist/packem_shared/defineAggregateIndex-ZdyU78gh.mjs +291 -0
- package/dist/packem_shared/defineMigration-CAJLr6fx.mjs +8 -0
- package/dist/packem_shared/defineMutator-EIXAWhs9.mjs +11 -0
- package/dist/packem_shared/defineShape-CJ27Wx7o.mjs +17 -0
- package/dist/packem_shared/defineStorageRule-qu0mpilX.mjs +20 -0
- package/dist/packem_shared/functions-Di9FUNkf.mjs +5 -0
- package/dist/packem_shared/httpAction-FLwfsePg.mjs +340 -0
- package/dist/packem_shared/initLunora-lxwHTEV3.mjs +100 -0
- package/dist/packem_shared/mask-BV_jNzsN.mjs +211 -0
- package/dist/packem_shared/onConnect-CIPXKPyw.mjs +13 -0
- package/dist/packem_shared/policy-tag-DvpVH2tv.mjs +13 -0
- package/dist/packem_shared/protectPublic-BjFkQ_Or.mjs +15 -0
- package/dist/packem_shared/rls-2Jhd0uev.mjs +569 -0
- package/dist/packem_shared/run-middleware-CYQOuoV6.mjs +18 -0
- package/dist/packem_shared/storageRules-Cje6Woea.mjs +88 -0
- package/dist/packem_shared/types.d-BDY0FYHK.d.ts +135 -0
- package/dist/packem_shared/types.d-DmvyEMD6.d.mts +135 -0
- package/dist/rls/testing.d.mts +63 -0
- package/dist/rls/testing.d.ts +63 -0
- package/dist/rls/testing.mjs +49 -0
- package/dist/types.d.mts +1157 -0
- package/dist/types.d.ts +1157 -0
- package/dist/types.mjs +31 -0
- package/package.json +59 -17
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { r as runMiddlewareChain } from './run-middleware-CYQOuoV6.mjs';
|
|
2
|
+
|
|
3
|
+
const prefixTableName = (key, bareName) => `${key}_${bareName}`;
|
|
4
|
+
const rewriteReference = (target, key, bareNames) => bareNames.has(target) ? prefixTableName(key, target) : target;
|
|
5
|
+
const rewriteTableReferences = (table, key, bareNames, ownBareName) => {
|
|
6
|
+
const ownPrefixed = prefixTableName(key, ownBareName);
|
|
7
|
+
const rewriteOn = (on) => on === "" ? ownPrefixed : rewriteReference(on, key, bareNames);
|
|
8
|
+
const relationMap = {};
|
|
9
|
+
for (const [accessor, relation] of Object.entries(table.relationMap)) {
|
|
10
|
+
relationMap[accessor] = { ...relation, table: rewriteReference(relation.table, key, bareNames) };
|
|
11
|
+
}
|
|
12
|
+
const aggregateIndexes = table.aggregateIndexes.map((index) => {
|
|
13
|
+
return { ...index, on: rewriteOn(index.on) };
|
|
14
|
+
});
|
|
15
|
+
const rankIndexes = table.rankIndexes.map((index) => {
|
|
16
|
+
return { ...index, on: rewriteOn(index.on) };
|
|
17
|
+
});
|
|
18
|
+
return { ...table, aggregateIndexes, rankIndexes, relationMap };
|
|
19
|
+
};
|
|
20
|
+
const defineSchemaExtension = (key, options) => {
|
|
21
|
+
if (!key) {
|
|
22
|
+
throw new Error("defineSchemaExtension: `key` is required and must be a non-empty string");
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
key,
|
|
26
|
+
tables: options.tables,
|
|
27
|
+
...options.vectorIndexes ? { vectorIndexes: options.vectorIndexes } : {}
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
const definePlugin = (key, options) => {
|
|
31
|
+
if (!key) {
|
|
32
|
+
throw new Error("definePlugin: `key` is required and must be a non-empty string");
|
|
33
|
+
}
|
|
34
|
+
if (options.extension && options.extension.key !== key) {
|
|
35
|
+
throw new Error(`definePlugin("${key}"): extension key "${options.extension.key}" does not match plugin key`);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
key,
|
|
39
|
+
...options.extension ? { extension: options.extension } : {},
|
|
40
|
+
...options.middleware ? { middleware: options.middleware } : {}
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
const defineComponent = (key, options) => {
|
|
44
|
+
const plugin = definePlugin(key, {
|
|
45
|
+
...options.extension ? { extension: options.extension } : {},
|
|
46
|
+
...options.middleware ? { middleware: options.middleware } : {}
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
...plugin,
|
|
50
|
+
functions: options.functions ?? {}
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
const mergeSchemaExtension = (base, extension) => {
|
|
54
|
+
const { key } = extension;
|
|
55
|
+
const bareNames = new Set(Object.keys(extension.tables));
|
|
56
|
+
const merged = { ...base.tables };
|
|
57
|
+
for (const [bareName, table] of Object.entries(extension.tables)) {
|
|
58
|
+
const prefixed = prefixTableName(key, bareName);
|
|
59
|
+
if (Object.hasOwn(merged, prefixed)) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`defineSchema(...).extend("${key}"): table "${prefixed}" already exists in the base schema — another extension with the same key already contributed it`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
merged[prefixed] = rewriteTableReferences(table, key, bareNames, bareName);
|
|
65
|
+
}
|
|
66
|
+
const mergedVectorIndexes = { ...base.vectorIndexes };
|
|
67
|
+
if (extension.vectorIndexes) {
|
|
68
|
+
for (const [bareIndexName, index] of Object.entries(extension.vectorIndexes)) {
|
|
69
|
+
const prefixed = prefixTableName(key, bareIndexName);
|
|
70
|
+
if (Object.hasOwn(mergedVectorIndexes, prefixed)) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`defineSchema(...).extend("${key}"): vector index "${prefixed}" already exists in the base schema — another extension with the same key already contributed it`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
mergedVectorIndexes[prefixed] = { ...index, table: rewriteReference(index.table, key, bareNames) };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
// Preserve secure-by-default RLS across `.extend(...)` — a plugin's
|
|
80
|
+
// tables join an `.rls("required")` schema as protected-by-default too.
|
|
81
|
+
rlsMode: base.rlsMode,
|
|
82
|
+
tables: merged,
|
|
83
|
+
vectorIndexes: mergedVectorIndexes
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
const installPlugins = (base, plugins) => {
|
|
87
|
+
let schema = base;
|
|
88
|
+
for (const plugin of plugins) {
|
|
89
|
+
if (plugin.extension) {
|
|
90
|
+
schema = mergeSchemaExtension(schema, plugin.extension);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return schema;
|
|
94
|
+
};
|
|
95
|
+
const composePluginMiddleware = (plugins) => {
|
|
96
|
+
const middlewares = plugins.map((plugin) => plugin.middleware).filter((middleware) => middleware !== void 0);
|
|
97
|
+
return (async ({ ctx, next }) => runMiddlewareChain(middlewares, ctx, (context) => next({ ctx: context })));
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export { composePluginMiddleware, defineComponent, definePlugin, defineSchemaExtension, installPlugins, mergeSchemaExtension };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const definePolicy = (input) => {
|
|
2
|
+
return { on: input.on, table: input.table, when: input.when };
|
|
3
|
+
};
|
|
4
|
+
const createPolicyDsl = () => (input) => {
|
|
5
|
+
return { on: input.on, table: input.table, when: input.when };
|
|
6
|
+
};
|
|
7
|
+
const definePermission = (name, options = {}) => {
|
|
8
|
+
return { name, ...options };
|
|
9
|
+
};
|
|
10
|
+
const definePolicies = (policies) => {
|
|
11
|
+
const seenWhenByKey = /* @__PURE__ */ new Map();
|
|
12
|
+
for (const policy of policies) {
|
|
13
|
+
const key = JSON.stringify([policy.table, policy.on]);
|
|
14
|
+
const whens = seenWhenByKey.get(key) ?? /* @__PURE__ */ new Set();
|
|
15
|
+
if (whens.has(policy.when)) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`definePolicies: duplicate policy for (table "${policy.table}", on "${policy.on}") — the same decision function is registered more than once. Multiple distinct policies per (table, on) are allowed (reads OR, writes AND); remove the duplicate.`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
whens.add(policy.when);
|
|
21
|
+
seenWhenByKey.set(key, whens);
|
|
22
|
+
}
|
|
23
|
+
return policies;
|
|
24
|
+
};
|
|
25
|
+
const defineRole = (name, options = {}) => {
|
|
26
|
+
return { name, ...options };
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export { createPolicyDsl, definePermission, definePolicies, definePolicy, defineRole };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const NON_SECRET_BINDING_METHODS = [
|
|
2
|
+
"put",
|
|
3
|
+
// KV / R2
|
|
4
|
+
"list",
|
|
5
|
+
// KV / R2
|
|
6
|
+
"delete",
|
|
7
|
+
// KV / R2
|
|
8
|
+
"getWithMetadata",
|
|
9
|
+
// KV
|
|
10
|
+
"head",
|
|
11
|
+
// R2
|
|
12
|
+
"createMultipartUpload",
|
|
13
|
+
// R2
|
|
14
|
+
"idFromName",
|
|
15
|
+
// Durable Object namespace
|
|
16
|
+
"newUniqueId",
|
|
17
|
+
// Durable Object namespace
|
|
18
|
+
"getByName",
|
|
19
|
+
// Durable Object namespace
|
|
20
|
+
"send",
|
|
21
|
+
// Queue producer
|
|
22
|
+
"sendBatch",
|
|
23
|
+
// Queue producer
|
|
24
|
+
"writeDataPoint",
|
|
25
|
+
// Analytics Engine dataset
|
|
26
|
+
"create",
|
|
27
|
+
// Workflows binding (get(id) + create/createBatch)
|
|
28
|
+
"createBatch"
|
|
29
|
+
// Workflows binding
|
|
30
|
+
];
|
|
31
|
+
const isSecretBinding = (value) => {
|
|
32
|
+
if (typeof value !== "object" || value === null) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const record = value;
|
|
36
|
+
if (typeof record.get !== "function") {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return !NON_SECRET_BINDING_METHODS.some((method) => typeof record[method] === "function");
|
|
40
|
+
};
|
|
41
|
+
const createSecrets = (env) => {
|
|
42
|
+
return {
|
|
43
|
+
get: async (name) => {
|
|
44
|
+
const binding = env[name];
|
|
45
|
+
if (!isSecretBinding(binding)) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`ctx.secrets: no Secrets Store binding named "${name}". Add a \`secrets_store_secrets[]\` entry (binding "${name}", pointing at your store + secret) to wrangler.jsonc — \`ctx.secrets\` reads a Secrets Store binding, not a plain \`.dev.vars\` value.`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return binding.get();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export { createSecrets };
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { isOrWrapsFromValidator, v } from '@lunora/values';
|
|
2
|
+
import { mergeSchemaExtension } from './composePluginMiddleware-Ck5_TUO8.mjs';
|
|
3
|
+
|
|
4
|
+
const relationBuilder = {
|
|
5
|
+
many: (table, options) => {
|
|
6
|
+
return { field: options.field, kind: "many", references: options.references ?? "_id", table };
|
|
7
|
+
},
|
|
8
|
+
one: (table, options) => {
|
|
9
|
+
return { field: options.field, kind: "one", onDelete: options.onDelete, references: options.references ?? "_id", table };
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
const makeTrigger = (timing, op, handler) => {
|
|
13
|
+
return { handler, op, timing };
|
|
14
|
+
};
|
|
15
|
+
const createTriggerBuilder = () => {
|
|
16
|
+
return {
|
|
17
|
+
afterDelete: (handler) => makeTrigger("after", "delete", handler),
|
|
18
|
+
afterInsert: (handler) => makeTrigger("after", "insert", handler),
|
|
19
|
+
afterUpdate: (handler) => makeTrigger("after", "update", handler),
|
|
20
|
+
beforeDelete: (handler) => makeTrigger("before", "delete", handler),
|
|
21
|
+
beforeInsert: (handler) => makeTrigger("before", "insert", handler),
|
|
22
|
+
beforeUpdate: (handler) => makeTrigger("before", "update", handler)
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
const defineTable = (inputShape) => {
|
|
26
|
+
const shape = { ...inputShape };
|
|
27
|
+
for (const [columnName, validator] of Object.entries(shape)) {
|
|
28
|
+
if (isOrWrapsFromValidator(validator)) {
|
|
29
|
+
throw new Error(`defineTable: column "${columnName}" uses v.from() which is args-only — table columns need a concrete v.* type`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const aggregateIndexes = [];
|
|
33
|
+
const indexes = [];
|
|
34
|
+
const rankIndexes = [];
|
|
35
|
+
const relations = {};
|
|
36
|
+
const searchIndexes = [];
|
|
37
|
+
const triggers = {};
|
|
38
|
+
const triggerBuilder = createTriggerBuilder();
|
|
39
|
+
const vectorIndexes = [];
|
|
40
|
+
let shardMode = { kind: "root" };
|
|
41
|
+
let isExternallyManaged = false;
|
|
42
|
+
let isPublic = false;
|
|
43
|
+
let softDelete;
|
|
44
|
+
let externalSource;
|
|
45
|
+
const builder = {
|
|
46
|
+
aggregateIndex(name, options) {
|
|
47
|
+
const op = options?.op ?? "count";
|
|
48
|
+
if (op !== "count" && !options?.field) {
|
|
49
|
+
throw new Error(`aggregateIndex "${name}": op "${op}" requires a "field"`);
|
|
50
|
+
}
|
|
51
|
+
aggregateIndexes.push({
|
|
52
|
+
by: options?.by,
|
|
53
|
+
field: options?.field,
|
|
54
|
+
name,
|
|
55
|
+
// `on` is filled in by `defineSchema` once the table is keyed; we
|
|
56
|
+
// stash the placeholder so the AggregateIndexDefinition shape stays
|
|
57
|
+
// straightforward for D1/DO consumers (who read `on`).
|
|
58
|
+
on: "",
|
|
59
|
+
op,
|
|
60
|
+
where: options?.where
|
|
61
|
+
});
|
|
62
|
+
return builder;
|
|
63
|
+
},
|
|
64
|
+
get aggregateIndexes() {
|
|
65
|
+
return aggregateIndexes;
|
|
66
|
+
},
|
|
67
|
+
get externalSource() {
|
|
68
|
+
return externalSource;
|
|
69
|
+
},
|
|
70
|
+
externallyManaged() {
|
|
71
|
+
isExternallyManaged = true;
|
|
72
|
+
return builder;
|
|
73
|
+
},
|
|
74
|
+
global(options) {
|
|
75
|
+
shardMode = { backend: options?.backend ?? "d1", kind: "global" };
|
|
76
|
+
return builder;
|
|
77
|
+
},
|
|
78
|
+
get isExternallyManaged() {
|
|
79
|
+
return isExternallyManaged;
|
|
80
|
+
},
|
|
81
|
+
get isPublic() {
|
|
82
|
+
return isPublic;
|
|
83
|
+
},
|
|
84
|
+
index(name, fields, options) {
|
|
85
|
+
indexes.push({ fields, name, unique: options?.unique ?? false });
|
|
86
|
+
return builder;
|
|
87
|
+
},
|
|
88
|
+
get indexes() {
|
|
89
|
+
return indexes;
|
|
90
|
+
},
|
|
91
|
+
public() {
|
|
92
|
+
isPublic = true;
|
|
93
|
+
return builder;
|
|
94
|
+
},
|
|
95
|
+
rankIndex(name, options) {
|
|
96
|
+
if (!options.sortBy || options.sortBy.length === 0) {
|
|
97
|
+
throw new Error(`rankIndex "${name}": "sortBy" is required and must list at least one key`);
|
|
98
|
+
}
|
|
99
|
+
const sortBy = options.sortBy.map((key) => {
|
|
100
|
+
return {
|
|
101
|
+
direction: key.direction ?? "asc",
|
|
102
|
+
field: key.field
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
rankIndexes.push({
|
|
106
|
+
name,
|
|
107
|
+
// `on` is filled in by `defineSchema` once the table is keyed —
|
|
108
|
+
// same pattern as `aggregateIndex`.
|
|
109
|
+
on: "",
|
|
110
|
+
partitionBy: options.partitionBy,
|
|
111
|
+
sortBy,
|
|
112
|
+
where: options.where
|
|
113
|
+
});
|
|
114
|
+
return builder;
|
|
115
|
+
},
|
|
116
|
+
get rankIndexes() {
|
|
117
|
+
return rankIndexes;
|
|
118
|
+
},
|
|
119
|
+
get relationMap() {
|
|
120
|
+
return relations;
|
|
121
|
+
},
|
|
122
|
+
relations(build) {
|
|
123
|
+
Object.assign(relations, build(relationBuilder));
|
|
124
|
+
return builder;
|
|
125
|
+
},
|
|
126
|
+
searchIndex(name, options) {
|
|
127
|
+
searchIndexes.push({ field: options.field, filterFields: options.filterFields, name });
|
|
128
|
+
return builder;
|
|
129
|
+
},
|
|
130
|
+
get searchIndexes() {
|
|
131
|
+
return searchIndexes;
|
|
132
|
+
},
|
|
133
|
+
shape,
|
|
134
|
+
shardBy(field) {
|
|
135
|
+
shardMode = { field, kind: "shardBy" };
|
|
136
|
+
return builder;
|
|
137
|
+
},
|
|
138
|
+
get shardMode() {
|
|
139
|
+
return shardMode;
|
|
140
|
+
},
|
|
141
|
+
get softDeleteMode() {
|
|
142
|
+
return softDelete;
|
|
143
|
+
},
|
|
144
|
+
source(definition) {
|
|
145
|
+
if (!definition.binding) {
|
|
146
|
+
throw new Error("source: `binding` is required (the wrangler Hyperdrive binding name)");
|
|
147
|
+
}
|
|
148
|
+
if (!definition.query) {
|
|
149
|
+
throw new Error("source: `query` is required (the tenant-membership SQL)");
|
|
150
|
+
}
|
|
151
|
+
externalSource = definition;
|
|
152
|
+
isExternallyManaged = true;
|
|
153
|
+
return builder;
|
|
154
|
+
},
|
|
155
|
+
softDelete(options) {
|
|
156
|
+
const field = options?.field ?? "deletedAt";
|
|
157
|
+
softDelete = { field };
|
|
158
|
+
if (!(field in shape)) {
|
|
159
|
+
shape[field] = v.optional(v.number().nullable());
|
|
160
|
+
}
|
|
161
|
+
return builder;
|
|
162
|
+
},
|
|
163
|
+
get triggerMap() {
|
|
164
|
+
return triggers;
|
|
165
|
+
},
|
|
166
|
+
triggers(build) {
|
|
167
|
+
Object.assign(triggers, build(triggerBuilder));
|
|
168
|
+
return builder;
|
|
169
|
+
},
|
|
170
|
+
get vectorIndexes() {
|
|
171
|
+
return vectorIndexes;
|
|
172
|
+
},
|
|
173
|
+
vectorize(field, options) {
|
|
174
|
+
vectorIndexes.push({
|
|
175
|
+
dimensions: options.dimensions,
|
|
176
|
+
embed: options.embed,
|
|
177
|
+
field,
|
|
178
|
+
metadata: options.metadata,
|
|
179
|
+
metric: options.metric,
|
|
180
|
+
name: options.index
|
|
181
|
+
});
|
|
182
|
+
return builder;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
return builder;
|
|
186
|
+
};
|
|
187
|
+
const defineVectorIndex = (options) => {
|
|
188
|
+
return {
|
|
189
|
+
dimensions: options.dimensions,
|
|
190
|
+
embed: options.embed,
|
|
191
|
+
kind: "vectorIndex",
|
|
192
|
+
metadata: options.metadata,
|
|
193
|
+
metric: options.metric,
|
|
194
|
+
select: options.source.select,
|
|
195
|
+
table: options.source.table
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
const defineAggregateIndex = (name, options) => {
|
|
199
|
+
const op = options.op ?? "count";
|
|
200
|
+
if (op !== "count" && !options.field) {
|
|
201
|
+
throw new Error(`aggregateIndex "${name}": op "${op}" requires a "field"`);
|
|
202
|
+
}
|
|
203
|
+
return { by: options.by, field: options.field, name, on: options.on, op, where: options.where };
|
|
204
|
+
};
|
|
205
|
+
const defineRankIndex = (name, options) => {
|
|
206
|
+
if (!options.sortBy || options.sortBy.length === 0) {
|
|
207
|
+
throw new Error(`rankIndex "${name}": "sortBy" is required and must list at least one key`);
|
|
208
|
+
}
|
|
209
|
+
const sortBy = options.sortBy.map((key) => {
|
|
210
|
+
return {
|
|
211
|
+
direction: key.direction ?? "asc",
|
|
212
|
+
field: key.field
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
return { name, on: options.table, partitionBy: options.partitionBy, sortBy, where: options.where };
|
|
216
|
+
};
|
|
217
|
+
const withExtend = (schema) => {
|
|
218
|
+
return {
|
|
219
|
+
...schema,
|
|
220
|
+
extend(extension) {
|
|
221
|
+
return withExtend(mergeSchemaExtension(schema, extension));
|
|
222
|
+
},
|
|
223
|
+
jurisdiction(_jurisdiction) {
|
|
224
|
+
return withExtend(schema);
|
|
225
|
+
},
|
|
226
|
+
rls(mode) {
|
|
227
|
+
return withExtend({ ...schema, rlsMode: mode });
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
const fillIndexTableNames = (tables) => {
|
|
232
|
+
for (const [tableName, table] of Object.entries(tables)) {
|
|
233
|
+
for (const index of table.aggregateIndexes) {
|
|
234
|
+
if (index.on === "") {
|
|
235
|
+
index.on = tableName;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
for (const index of table.rankIndexes) {
|
|
239
|
+
if (index.on === "") {
|
|
240
|
+
index.on = tableName;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
const attachStandaloneIndexes = (tables, aggregateIndexes, rankIndexes) => {
|
|
246
|
+
for (const index of Object.values(aggregateIndexes)) {
|
|
247
|
+
const table = tables[index.on];
|
|
248
|
+
if (!table) {
|
|
249
|
+
throw new Error(`defineAggregateIndex "${index.name}": unknown table "${index.on}"`);
|
|
250
|
+
}
|
|
251
|
+
table.aggregateIndexes.push(index);
|
|
252
|
+
}
|
|
253
|
+
for (const index of Object.values(rankIndexes)) {
|
|
254
|
+
const table = tables[index.on];
|
|
255
|
+
if (!table) {
|
|
256
|
+
throw new Error(`defineRankIndex "${index.name}": unknown table "${index.on}"`);
|
|
257
|
+
}
|
|
258
|
+
table.rankIndexes.push(index);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
const validateExternalSources = (tables) => {
|
|
262
|
+
for (const [name, table] of Object.entries(tables)) {
|
|
263
|
+
const source = table.externalSource;
|
|
264
|
+
if (!source) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (table.shardMode.kind === "global") {
|
|
268
|
+
throw new Error(
|
|
269
|
+
`defineSchema: table "${name}" cannot be both .source() and .global() — a sourced table materializes into a shard DO's SQLite, a global table lives in the external tier`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
if (table.shardMode.kind === "shardBy" && !source.tenantBy) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`defineSchema: sourced + .shardBy() table "${name}" needs a \`tenantBy\` mapper — without it every tenant's DO would run the same unscoped query and replicate the whole multitenant table (a cross-tenant leak). Add \`tenantBy: (shardKey) => [shardKey]\` binding the shard key into the query's parameters.`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
if (source.mode === "incremental") {
|
|
278
|
+
throw new Error(
|
|
279
|
+
`defineSchema: table "${name}" uses \`mode: "incremental"\`, which is not yet implemented — only "full-pull" (the default) is supported. Remove \`mode\` or set it to "full-pull".`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
const defineSchema = (tables, vectorIndexes = {}, aggregateIndexes = {}, rankIndexes = {}) => {
|
|
285
|
+
fillIndexTableNames(tables);
|
|
286
|
+
attachStandaloneIndexes(tables, aggregateIndexes, rankIndexes);
|
|
287
|
+
validateExternalSources(tables);
|
|
288
|
+
return withExtend({ tables, vectorIndexes });
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
export { defineAggregateIndex, defineRankIndex, defineSchema, defineTable, defineVectorIndex };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { v as validateArgs } from './functions-Di9FUNkf.mjs';
|
|
2
|
+
|
|
3
|
+
const defineMutator = (definition) => {
|
|
4
|
+
const handler = async (context, rawArgs) => {
|
|
5
|
+
const parsed = validateArgs(definition.args ?? {}, rawArgs);
|
|
6
|
+
return definition.server(context, parsed);
|
|
7
|
+
};
|
|
8
|
+
return { __lunoraMutator: true, ...definition, handler, kind: "mutation" };
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export { defineMutator };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { v as validateArgs } from './functions-Di9FUNkf.mjs';
|
|
2
|
+
|
|
3
|
+
const defineShape = (definition) => {
|
|
4
|
+
if (definition.table.trim() === "") {
|
|
5
|
+
throw new Error("defineShape: `table` must be a non-empty string");
|
|
6
|
+
}
|
|
7
|
+
if (definition.columns?.length === 0) {
|
|
8
|
+
throw new Error("defineShape: `columns` must list at least one column when provided");
|
|
9
|
+
}
|
|
10
|
+
const compileWhere = (context, rawArgs) => {
|
|
11
|
+
const parsed = validateArgs(definition.args ?? {}, rawArgs);
|
|
12
|
+
return definition.where(context, parsed);
|
|
13
|
+
};
|
|
14
|
+
return { __lunoraShape: true, ...definition, compileWhere };
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export { defineShape };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const defineStorageRule = (input) => {
|
|
2
|
+
return { bucket: input.bucket, on: input.on, prefix: input.prefix, when: input.when };
|
|
3
|
+
};
|
|
4
|
+
const defineStorageRules = (rules) => {
|
|
5
|
+
const seenWhenByKey = /* @__PURE__ */ new Map();
|
|
6
|
+
for (const rule of rules) {
|
|
7
|
+
const key = JSON.stringify([rule.bucket, rule.on, rule.prefix]);
|
|
8
|
+
const whens = seenWhenByKey.get(key) ?? /* @__PURE__ */ new Set();
|
|
9
|
+
if (whens.has(rule.when)) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
`defineStorageRules: duplicate rule for (bucket "${rule.bucket}", on "${rule.on}"${rule.prefix === void 0 ? "" : `, prefix "${rule.prefix}"`}) — the same decision function is registered more than once. Multiple distinct rules per (bucket, on) are allowed (they OR); remove the duplicate.`
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
whens.add(rule.when);
|
|
15
|
+
seenWhenByKey.set(key, whens);
|
|
16
|
+
}
|
|
17
|
+
return rules;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export { defineStorageRule, defineStorageRules };
|