@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.
Files changed (45) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +134 -9
  3. package/__assets__/package-og.svg +14 -0
  4. package/dist/data-model.d.mts +416 -0
  5. package/dist/data-model.d.ts +416 -0
  6. package/dist/data-model.mjs +1 -0
  7. package/dist/drizzle.d.mts +1 -0
  8. package/dist/drizzle.d.ts +1 -0
  9. package/dist/drizzle.mjs +1 -0
  10. package/dist/index.d.mts +1985 -0
  11. package/dist/index.d.ts +1985 -0
  12. package/dist/index.mjs +28 -0
  13. package/dist/packem_shared/LunoraEnvError-DjFkpkSP.mjs +187 -0
  14. package/dist/packem_shared/LunoraError-DN7Zhhvu.mjs +54 -0
  15. package/dist/packem_shared/PRESENCE_DEFAULT_TTL_MS-D8viLY1S.mjs +114 -0
  16. package/dist/packem_shared/asBucketStorage-Cnxd9y2q.mjs +11 -0
  17. package/dist/packem_shared/bindOrm-Ce57S3N9.mjs +128 -0
  18. package/dist/packem_shared/buildRlsReadRegistry-1jexWrb3.mjs +107 -0
  19. package/dist/packem_shared/composePluginMiddleware-Ck5_TUO8.mjs +100 -0
  20. package/dist/packem_shared/createPolicyDsl-De67zPDS.mjs +29 -0
  21. package/dist/packem_shared/createSecrets-TsIP9lOa.mjs +55 -0
  22. package/dist/packem_shared/defineAggregateIndex-ZdyU78gh.mjs +291 -0
  23. package/dist/packem_shared/defineMigration-CAJLr6fx.mjs +8 -0
  24. package/dist/packem_shared/defineMutator-EIXAWhs9.mjs +11 -0
  25. package/dist/packem_shared/defineShape-CJ27Wx7o.mjs +17 -0
  26. package/dist/packem_shared/defineStorageRule-qu0mpilX.mjs +20 -0
  27. package/dist/packem_shared/functions-Di9FUNkf.mjs +5 -0
  28. package/dist/packem_shared/httpAction-FLwfsePg.mjs +340 -0
  29. package/dist/packem_shared/initLunora-lxwHTEV3.mjs +100 -0
  30. package/dist/packem_shared/mask-BV_jNzsN.mjs +211 -0
  31. package/dist/packem_shared/onConnect-CIPXKPyw.mjs +13 -0
  32. package/dist/packem_shared/policy-tag-DvpVH2tv.mjs +13 -0
  33. package/dist/packem_shared/protectPublic-BjFkQ_Or.mjs +15 -0
  34. package/dist/packem_shared/rls-2Jhd0uev.mjs +569 -0
  35. package/dist/packem_shared/run-middleware-CYQOuoV6.mjs +18 -0
  36. package/dist/packem_shared/storageRules-Cje6Woea.mjs +88 -0
  37. package/dist/packem_shared/types.d-BDY0FYHK.d.ts +135 -0
  38. package/dist/packem_shared/types.d-DmvyEMD6.d.mts +135 -0
  39. package/dist/rls/testing.d.mts +63 -0
  40. package/dist/rls/testing.d.ts +63 -0
  41. package/dist/rls/testing.mjs +49 -0
  42. package/dist/types.d.mts +1157 -0
  43. package/dist/types.d.ts +1157 -0
  44. package/dist/types.mjs +31 -0
  45. 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,8 @@
1
+ const defineMigration = (definition) => {
2
+ if (definition.id.trim() === "") {
3
+ throw new Error("defineMigration: `id` must be a non-empty string");
4
+ }
5
+ return { __lunoraMigration: true, ...definition };
6
+ };
7
+
8
+ export { defineMigration };
@@ -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 };
@@ -0,0 +1,5 @@
1
+ import { parseValidatorMap } from '@lunora/values';
2
+
3
+ const validateArgs = (validators, args) => parseValidatorMap(validators, args, "args");
4
+
5
+ export { validateArgs as v };