@drhomet/mithaq 0.0.0-stage → 0.1.1

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 (151) hide show
  1. package/LICENSE +22 -0
  2. package/LICENSES/payload.txt +22 -0
  3. package/LICENSES/ponytail.txt +21 -0
  4. package/dist/asas/actor.d.ts +96 -0
  5. package/dist/asas/actor.js +114 -0
  6. package/dist/asas/build.d.ts +231 -0
  7. package/dist/asas/build.js +787 -0
  8. package/dist/asas/cli/mithaq.d.ts +2 -0
  9. package/dist/asas/cli/mithaq.js +145 -0
  10. package/dist/asas/cli/upgrade.d.ts +20 -0
  11. package/dist/asas/cli/upgrade.js +147 -0
  12. package/dist/asas/http/fetch.d.ts +23 -0
  13. package/dist/asas/http/fetch.js +115 -0
  14. package/dist/asas/http/orpc.d.ts +43 -0
  15. package/dist/asas/http/orpc.js +135 -0
  16. package/dist/asas/runner/bulk.d.ts +20 -0
  17. package/dist/asas/runner/bulk.js +191 -0
  18. package/dist/asas/runner/errors.d.ts +60 -0
  19. package/dist/asas/runner/errors.js +59 -0
  20. package/dist/asas/runner/read.d.ts +68 -0
  21. package/dist/asas/runner/read.js +579 -0
  22. package/dist/asas/runner/runtime.d.ts +21 -0
  23. package/dist/asas/runner/runtime.js +8 -0
  24. package/dist/asas/runner/steps.d.ts +400 -0
  25. package/dist/asas/runner/steps.js +271 -0
  26. package/dist/asas/runner/tenant.d.ts +54 -0
  27. package/dist/asas/runner/tenant.js +213 -0
  28. package/dist/asas/runner/write.d.ts +444 -0
  29. package/dist/asas/runner/write.js +791 -0
  30. package/dist/auth/better-auth/access.d.ts +15 -0
  31. package/dist/auth/better-auth/access.js +71 -0
  32. package/dist/auth/better-auth/generate.d.ts +29 -0
  33. package/dist/auth/better-auth/generate.js +310 -0
  34. package/dist/auth/better-auth/index.d.ts +92 -0
  35. package/dist/auth/better-auth/index.js +81 -0
  36. package/dist/auth/better-auth/tenant.d.ts +18 -0
  37. package/dist/auth/better-auth/tenant.js +26 -0
  38. package/dist/auth/local/index.d.ts +285 -0
  39. package/dist/auth/local/index.js +736 -0
  40. package/dist/base/decimal.d.ts +9 -0
  41. package/dist/base/decimal.js +16 -0
  42. package/dist/base/fields.d.ts +123 -0
  43. package/dist/base/fields.js +51 -0
  44. package/dist/base/index.d.ts +46 -0
  45. package/dist/base/index.js +5 -0
  46. package/dist/base/kinds.d.ts +54 -0
  47. package/dist/base/kinds.js +78 -0
  48. package/dist/base/rules.d.ts +73 -0
  49. package/dist/base/rules.js +80 -0
  50. package/dist/drizzle/index.d.ts +438 -0
  51. package/dist/drizzle/index.js +832 -0
  52. package/dist/drizzle/kinds.d.ts +27 -0
  53. package/dist/drizzle/kinds.js +41 -0
  54. package/dist/drizzle/migrate.d.ts +133 -0
  55. package/dist/drizzle/migrate.js +1285 -0
  56. package/dist/drizzle/proxy/index.d.ts +1 -0
  57. package/dist/drizzle/proxy/index.js +1 -0
  58. package/dist/drizzle/proxy/node-postgres.d.ts +1 -0
  59. package/dist/drizzle/proxy/node-postgres.js +1 -0
  60. package/dist/drizzle/proxy/pg-core.d.ts +1 -0
  61. package/dist/drizzle/proxy/pg-core.js +1 -0
  62. package/dist/drizzle/rules.d.ts +336 -0
  63. package/dist/drizzle/rules.js +44 -0
  64. package/dist/index.d.ts +45 -0
  65. package/dist/index.js +36 -0
  66. package/dist/mithaq/define.d.ts +288 -0
  67. package/dist/mithaq/define.js +43 -0
  68. package/dist/mithaq/generate-messages.d.ts +38 -0
  69. package/dist/mithaq/generate-messages.js +234 -0
  70. package/dist/mithaq/language.d.ts +335 -0
  71. package/dist/mithaq/language.js +76 -0
  72. package/dist/mithaq/lock.d.ts +71 -0
  73. package/dist/mithaq/lock.js +329 -0
  74. package/dist/mithaq/messages.d.ts +55 -0
  75. package/dist/mithaq/messages.js +106 -0
  76. package/dist/mithaq/refusal.d.ts +6 -0
  77. package/dist/mithaq/refusal.js +6 -0
  78. package/dist/mithaq/resolve.d.ts +10 -0
  79. package/dist/mithaq/resolve.js +954 -0
  80. package/dist/mithaq/rows.d.ts +87 -0
  81. package/dist/mithaq/rows.js +174 -0
  82. package/dist/mithaq/rukn.d.ts +72 -0
  83. package/dist/mithaq/rukn.js +15 -0
  84. package/dist/mithaq/standard-schema.d.ts +38 -0
  85. package/dist/mithaq/standard-schema.js +2 -0
  86. package/dist/mithaq/types.d.ts +358 -0
  87. package/dist/mithaq/types.js +15 -0
  88. package/dist/mithaq/user.d.ts +19 -0
  89. package/dist/mithaq/user.js +16 -0
  90. package/dist/mithaq/where.d.ts +32 -0
  91. package/dist/mithaq/where.js +87 -0
  92. package/dist/rbac/index.d.ts +105 -0
  93. package/dist/rbac/index.js +182 -0
  94. package/dist/rbac/steps.d.ts +22 -0
  95. package/dist/rbac/steps.js +161 -0
  96. package/dist/rich-text/index.d.ts +27 -0
  97. package/dist/rich-text/index.js +58 -0
  98. package/dist/rich-text/rich-text.d.ts +159 -0
  99. package/dist/rich-text/rich-text.js +372 -0
  100. package/dist/screen/client.d.ts +153 -0
  101. package/dist/screen/client.js +68 -0
  102. package/dist/screen/generate.d.ts +14 -0
  103. package/dist/screen/generate.js +226 -0
  104. package/dist/screen/index.d.ts +479 -0
  105. package/dist/screen/index.js +645 -0
  106. package/dist/screen/react.d.ts +95 -0
  107. package/dist/screen/react.js +58 -0
  108. package/dist/screen/studio.d.ts +24 -0
  109. package/dist/screen/studio.js +68 -0
  110. package/dist/service/index.d.ts +63 -0
  111. package/dist/service/index.js +15 -0
  112. package/dist/storage/allowed-type.d.ts +5 -0
  113. package/dist/storage/allowed-type.js +7 -0
  114. package/dist/storage/index.d.ts +43 -0
  115. package/dist/storage/index.js +44 -0
  116. package/dist/storage/storage.d.ts +384 -0
  117. package/dist/storage/storage.js +405 -0
  118. package/dist/versions/index.d.ts +37 -0
  119. package/dist/versions/index.js +8 -0
  120. package/dist/versions/versions.d.ts +104 -0
  121. package/dist/versions/versions.js +160 -0
  122. package/dist/zod/index.d.ts +184 -0
  123. package/dist/zod/index.js +121 -0
  124. package/dist/zod/kinds.d.ts +34 -0
  125. package/dist/zod/kinds.js +49 -0
  126. package/dist/zod/rules.d.ts +117 -0
  127. package/dist/zod/rules.js +67 -0
  128. package/package.json +63 -4
  129. package/r/mithaq-cells.json +20 -0
  130. package/r/mithaq-controls.json +37 -0
  131. package/r/mithaq-directional-icon.json +15 -0
  132. package/r/mithaq-expected-updated-at.json +15 -0
  133. package/r/mithaq-field-shell.json +18 -0
  134. package/r/mithaq-filter-operators.json +15 -0
  135. package/r/mithaq-form.json +33 -0
  136. package/r/mithaq-list-columns.json +23 -0
  137. package/r/mithaq-list-filters.json +24 -0
  138. package/r/mithaq-list-pagination.json +24 -0
  139. package/r/mithaq-list-query.json +18 -0
  140. package/r/mithaq-list-selection.json +19 -0
  141. package/r/mithaq-list.json +33 -0
  142. package/r/mithaq-nav.json +24 -0
  143. package/r/mithaq-nodes.json +34 -0
  144. package/r/mithaq-relationship-cell.json +20 -0
  145. package/r/mithaq-relationship-control.json +20 -0
  146. package/r/mithaq-rich-text.json +40 -0
  147. package/r/mithaq-screens-host.json +96 -0
  148. package/r/mithaq-storage.json +44 -0
  149. package/r/mithaq-upload.json +29 -0
  150. package/r/registry.json +550 -0
  151. package/README.md +0 -3
@@ -0,0 +1,20 @@
1
+ import { type ResolvedOperation, type WriteTools } from './steps.ts';
2
+ import { type Authorize, type ManagedAdmin, type Options } from './write.ts';
3
+ import type { ResolvedKayan } from '../../mithaq/types.ts';
4
+ import type { Store } from '../../mithaq/language.ts';
5
+ export type SingleWrite<Actor> = (data: Record<string, unknown>, options?: Options<Actor>) => Promise<unknown>;
6
+ export declare const bulk: <Actor = unknown>({ kayan, declaration, tools, store, single, cap, bulkLimit: ceiling, authorize, admin, tenantOf, }: {
7
+ kayan: ResolvedKayan;
8
+ declaration: ResolvedOperation;
9
+ tools: WriteTools<unknown, Actor>;
10
+ store: Store;
11
+ single: SingleWrite<Actor>;
12
+ cap: number;
13
+ /**
14
+ * The most rows one call may match, the service target's `bulkLimit`.
15
+ */
16
+ bulkLimit: number;
17
+ authorize: Authorize;
18
+ admin?: ManagedAdmin<Actor>;
19
+ tenantOf: (actor: unknown) => string | undefined;
20
+ }) => (given: Record<string, unknown>, options?: Options<Actor>) => Promise<Record<string, unknown>>;
@@ -0,0 +1,191 @@
1
+ // One transaction for every row: a row that cannot be written rolls the whole call
2
+ // back, because a bulk that half happened is a state no client can undo.
3
+ import { AfterCommitError, AsasError, invalid } from './errors.js';
4
+ import { assertReadableWhere, checkedWhere } from './read.js';
5
+ import { running } from './runtime.js';
6
+ import { refuseTenantOption, writeTenant } from './tenant.js';
7
+ import { atEffect, parse, validated } from './steps.js';
8
+ import { aliveWhere, beginWrite, drain, ontoRow, projectReadableFields, publishedWhere, runOutside } from './write.js';
9
+ import { combineWhere } from '../../mithaq/where.js';
10
+ // The bulk's own key is asked once, then each row goes through the single-row
11
+ // write, which asks its own key and field access: no key grants more through a bulk.
12
+ export const bulk = ({ kayan, declaration, tools, store, single, cap, bulkLimit: ceiling, authorize, admin, tenantOf, }) => async (given, options) => {
13
+ const list = atEffect(declaration.steps);
14
+ // Each row lands on the row as it is, so `{ draft: true }` is refused rather than published.
15
+ if (kayan.versions?.drafts && options?.draft) {
16
+ throw invalid({
17
+ message: `Kayan "${kayan.name}" operation "${tools.operation}" writes each row as it is, so it saves no draft. Call it without draft.`,
18
+ key: 'error.invalid.draft_operation',
19
+ params: { kayan: kayan.name, operation: tools.operation },
20
+ });
21
+ }
22
+ const { actor, context, entry, open } = beginWrite({ kayan, operation: tools.operation, store, cap, options });
23
+ refuseTenantOption({ kayan, operation: tools.operation, options });
24
+ writeTenant({ kayan, active: tenantOf(actor), effect: tools.effect, data: given?.data });
25
+ // Asked once, before `beforeTransaction`, so a refused actor runs no outside step.
26
+ const accessWhere = await authorize({
27
+ admin,
28
+ actor,
29
+ data: {},
30
+ effect: tools.effect,
31
+ kayan,
32
+ kind: tools.kind,
33
+ operation: tools.operation,
34
+ });
35
+ // Checked before the transaction opens, so a value a column refuses is a 400 no store was asked for.
36
+ if (given?.where !== undefined)
37
+ await checkedWhere(kayan, given.where);
38
+ let values = given;
39
+ if (declaration.beforeTransaction.length) {
40
+ const before = await runOutside({
41
+ kayan,
42
+ tools,
43
+ slot: 'beforeTransaction',
44
+ list: atEffect(declaration.beforeTransaction),
45
+ start: { kayan, data: { ...given }, actor, user: actor, tx: undefined, context },
46
+ around: { context, user: actor },
47
+ });
48
+ values = before.data;
49
+ }
50
+ let committed;
51
+ const many = async (data, tx) => {
52
+ const callerWhere = await assertReadableWhere({ kayan, where: data.where, actor, authorize, admin });
53
+ // Required here: a bulk with no `where` would be every row the access admits.
54
+ if (!callerWhere) {
55
+ throw invalid({
56
+ message: `Kayan "${kayan.name}" operation "${tools.operation}" needs a "where", because a bulk write with none would change every row the access admits.`,
57
+ key: 'error.invalid.bulk_where',
58
+ params: { kayan: kayan.name, operation: tools.operation },
59
+ });
60
+ }
61
+ // An update naming no column would move each row's `updatedAt` and keep a version
62
+ // for no edit. Asked after the steps, since a step may fill `data`.
63
+ if (tools.kind === 'updateMany' && !Object.keys((data.data ?? {})).length) {
64
+ throw invalid({
65
+ message: `Kayan "${kayan.name}" operation "${tools.operation}" was handed no "data", so there is nothing to change.`,
66
+ key: 'error.invalid.empty_update',
67
+ params: { kayan: kayan.name, operation: tools.operation },
68
+ });
69
+ }
70
+ // The rows a plain read shows, so a bulk never reaches a draft that was never published.
71
+ const where = combineWhere(combineWhere(callerWhere, accessWhere), publishedWhere(kayan));
72
+ // `lock: 'update'` holds every matched row until commit; the limit is the ceiling,
73
+ // and `totalDocs` counts the whole matched set without reading it.
74
+ const found = await store.find({
75
+ kayan: kayan.name,
76
+ args: { limit: ceiling, page: 1, lock: 'update', sort: { field: 'id', direction: 'asc' }, where },
77
+ tx,
78
+ });
79
+ // Refused rather than written in part: a caller could not tell which rows a partial
80
+ // bulk wrote. The caller narrows its `where` instead.
81
+ if (found.totalDocs > ceiling) {
82
+ throw invalid({
83
+ message: `Kayan "${kayan.name}" operation "${tools.operation}" matched ${found.totalDocs} rows, and a bulk write holds at most ${ceiling} in one transaction. Narrow the "where" and run it again.`,
84
+ key: 'error.invalid.bulk_limit',
85
+ params: { kayan: kayan.name, operation: tools.operation, matched: found.totalDocs, ceiling },
86
+ });
87
+ }
88
+ const values = (data.data ?? {});
89
+ const docs = [];
90
+ // In order, not in parallel: each write joins this transaction, which is one connection.
91
+ for (const row of found.docs) {
92
+ // Onto the row as it is, never its pending draft.
93
+ docs.push((await single({ ...values, id: row.id }, { actor: actor ?? undefined, [ontoRow]: true })));
94
+ }
95
+ // Unpublished rows the `where` names are left alone and reported under `errors`, so a
96
+ // person who ticked one is told; only to an actor who may read drafts, and only rows its answer admits.
97
+ const admits = kayan.versions?.drafts
98
+ ? await authorize({ admin, actor, data: {}, effect: 'read', kayan, operation: 'findVersions' }).then((where) => ({ where }), (thrown) => {
99
+ if (thrown instanceof AsasError && thrown.status === 403)
100
+ return undefined;
101
+ throw thrown;
102
+ })
103
+ : undefined;
104
+ const unpublished = admits
105
+ ? (await store.find({
106
+ kayan: kayan.name,
107
+ args: { limit: ceiling, page: 1, sort: { field: 'id', direction: 'asc' }, where: combineWhere(combineWhere(callerWhere, accessWhere), combineWhere(aliveWhere(kayan), combineWhere({ _status: { not_equals: 'published' } }, admits.where))) },
108
+ tx,
109
+ })).docs
110
+ : [];
111
+ return {
112
+ docs,
113
+ errors: unpublished.map((row) => ({ id: row.id, message: `Kayan "${kayan.name}" row is not published, so operation "${tools.operation}" left it as it is. Publish it first.` })),
114
+ };
115
+ };
116
+ const run = (tx) => {
117
+ entry.tx = tx;
118
+ return running.run({ context, transactions: new Map(open).set(store, entry), user: actor }, async () => {
119
+ const runTools = Object.freeze({ ...tools, many: (data) => many(data, tx) });
120
+ let ctx = { kayan, data: { ...values }, actor, user: actor, tx, context };
121
+ // No defaults here: each row's single-row write fills its own.
122
+ ctx = await parse({ schema: runTools.schema, ctx, tools: runTools });
123
+ for (const step of atEffect([
124
+ ...declaration.beforeSteps,
125
+ ...list,
126
+ ...declaration.afterSteps,
127
+ ]))
128
+ ctx = await step(ctx, runTools);
129
+ committed = ctx;
130
+ // Each document already passed its single-row output and read mask.
131
+ // The bulk contract validates the envelope after the steps have run.
132
+ if (!tools.publicOutput)
133
+ return ctx.data;
134
+ const parsed = await validated(tools.publicOutput, ctx.data);
135
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
136
+ throw new Error(`Kayan "${kayan.name}" custom bulk output must return an object.`);
137
+ }
138
+ const result = parsed;
139
+ if ('docs' in result) {
140
+ if (!Array.isArray(result.docs))
141
+ throw new Error(`Kayan "${kayan.name}" custom bulk output must return docs as an array.`);
142
+ for (const doc of result.docs) {
143
+ if (!doc || typeof doc !== 'object' || Array.isArray(doc))
144
+ throw new Error(`Kayan "${kayan.name}" custom bulk output must return row objects.`);
145
+ const visible = await projectReadableFields({ kayan, row: doc, actor });
146
+ const protectedField = Object.keys(doc).find((key) => !(key in visible));
147
+ if (protectedField)
148
+ throw new Error(`Kayan "${kayan.name}" custom bulk output returned protected field "${protectedField}" after read projection.`);
149
+ }
150
+ }
151
+ return result;
152
+ });
153
+ };
154
+ const afterCommit = async () => {
155
+ if (!declaration.afterCommit.length || !committed)
156
+ return;
157
+ await runOutside({
158
+ kayan,
159
+ tools,
160
+ slot: 'afterCommit',
161
+ list: atEffect(declaration.afterCommit),
162
+ start: { ...committed, tx: undefined },
163
+ around: { context, user: actor },
164
+ });
165
+ };
166
+ const joined = open.get(store);
167
+ if (joined) {
168
+ const result = await run(joined.tx);
169
+ if (declaration.afterCommit.length)
170
+ joined.afterCommit = [...(joined.afterCommit ?? []), afterCommit];
171
+ return result;
172
+ }
173
+ let result;
174
+ try {
175
+ result = await store.transaction(run);
176
+ }
177
+ finally {
178
+ entry.settled = true;
179
+ }
180
+ try {
181
+ await drain([...(entry.afterCommit ?? []), afterCommit]);
182
+ }
183
+ catch (cause) {
184
+ // Only the outermost operation answers with what it committed; under an outer one
185
+ // the failure climbs as a plain `Error`.
186
+ if (running.getStore())
187
+ throw cause;
188
+ throw new AfterCommitError(result, cause);
189
+ }
190
+ return result;
191
+ };
@@ -0,0 +1,60 @@
1
+ export declare const codes: {
2
+ readonly 400: 'BAD_REQUEST';
3
+ readonly 401: 'UNAUTHORIZED';
4
+ readonly 403: 'FORBIDDEN';
5
+ readonly 404: 'NOT_FOUND';
6
+ readonly 409: 'CONFLICT';
7
+ readonly 429: 'TOO_MANY_REQUESTS';
8
+ readonly 503: 'SERVICE_UNAVAILABLE';
9
+ };
10
+ /**
11
+ * A refusal the caller caused, with the HTTP status, a message `key` a client
12
+ * translates, and the `params` the message names. The English message is for logs.
13
+ */
14
+ export declare class AsasError extends Error {
15
+ readonly status: 400 | 403 | 404 | 409;
16
+ readonly key: MessageKey;
17
+ readonly params: Readonly<Record<string, unknown>>;
18
+ constructor(message: string, status: 400 | 403 | 404 | 409, key: MessageKey, params?: Record<string, unknown>, cause?: unknown);
19
+ }
20
+ /** A write committed, but one or more callbacks that followed it failed. */
21
+ export declare class AfterCommitError extends Error {
22
+ readonly committed = true;
23
+ readonly result: unknown;
24
+ constructor(result: unknown, cause: unknown);
25
+ }
26
+ /** Something a project is told that is expected rather than a failure, handed to the same `onError`. */
27
+ export declare class Notice extends Error {
28
+ name: string;
29
+ }
30
+ /** A generic wire signal; callback details remain in the server reporter. */
31
+ export declare const afterCommitHeader = "x-mithaq-after-commit";
32
+ /** A refusal's key in the message namespace. */
33
+ export type MessageKey = `error.${'conflict' | 'forbidden' | 'invalid' | 'not_found'}.${string}`;
34
+ /** What every refusal takes: the English a log reads, the key a client translates, the values it names, and the cause. */
35
+ export type Refusal = {
36
+ message: string;
37
+ key: MessageKey;
38
+ params?: Record<string, unknown>;
39
+ cause?: unknown;
40
+ };
41
+ /** The caller may not do this. */
42
+ export declare const denied: ({ message, key, params, cause }: Refusal) => AsasError;
43
+ /** The caller sent something the schema or the shape refuses. */
44
+ export declare const invalid: ({ message, key, params, cause }: Refusal) => AsasError;
45
+ /**
46
+ * The row the caller addressed is not there. An absent row and one access
47
+ * excludes answer alike, so a 404 never says which.
48
+ */
49
+ export declare const missing: ({ message, key, params, cause }: Refusal) => AsasError;
50
+ /**
51
+ * The write collides with a row already there, as a 409 so an offline client
52
+ * does not retry it. It never says which column or whose row it hit.
53
+ */
54
+ export declare const conflict: ({ message, key, params, cause }: Refusal) => AsasError;
55
+ export declare const localizedQuery: ({ kayan, field, what, locales, }: {
56
+ kayan: string;
57
+ field: string;
58
+ what: 'filter' | 'sort';
59
+ locales?: readonly string[];
60
+ }) => string;
@@ -0,0 +1,59 @@
1
+ // Both HTTP plugs read the code from here, so a refusal answers the same through either.
2
+ export const codes = {
3
+ 400: 'BAD_REQUEST',
4
+ 401: 'UNAUTHORIZED',
5
+ 403: 'FORBIDDEN',
6
+ 404: 'NOT_FOUND',
7
+ 409: 'CONFLICT',
8
+ 429: 'TOO_MANY_REQUESTS',
9
+ 503: 'SERVICE_UNAVAILABLE',
10
+ };
11
+ /**
12
+ * A refusal the caller caused, with the HTTP status, a message `key` a client
13
+ * translates, and the `params` the message names. The English message is for logs.
14
+ */
15
+ export class AsasError extends Error {
16
+ status;
17
+ key;
18
+ params;
19
+ constructor(message, status, key, params = {}, cause) {
20
+ super(message, cause === undefined ? undefined : { cause });
21
+ this.status = status;
22
+ this.key = key;
23
+ this.params = params;
24
+ }
25
+ }
26
+ /** A write committed, but one or more callbacks that followed it failed. */
27
+ export class AfterCommitError extends Error {
28
+ committed = true;
29
+ result;
30
+ constructor(result, cause) {
31
+ super('The write committed, but an afterCommit step failed.', { cause });
32
+ this.result = result;
33
+ }
34
+ }
35
+ /** Something a project is told that is expected rather than a failure, handed to the same `onError`. */
36
+ export class Notice extends Error {
37
+ name = 'Notice';
38
+ }
39
+ /** A generic wire signal; callback details remain in the server reporter. */
40
+ export const afterCommitHeader = 'x-mithaq-after-commit';
41
+ /** The caller may not do this. */
42
+ export const denied = ({ message, key, params, cause }) => new AsasError(message, 403, key, params, cause);
43
+ /** The caller sent something the schema or the shape refuses. */
44
+ export const invalid = ({ message, key, params, cause }) => new AsasError(message, 400, key, params, cause);
45
+ /**
46
+ * The row the caller addressed is not there. An absent row and one access
47
+ * excludes answer alike, so a 404 never says which.
48
+ */
49
+ export const missing = ({ message, key, params, cause }) => new AsasError(message, 404, key, params, cause);
50
+ /**
51
+ * The write collides with a row already there, as a 409 so an offline client
52
+ * does not retry it. It never says which column or whose row it hit.
53
+ */
54
+ export const conflict = ({ message, key, params, cause }) => new AsasError(message, 409, key, params, cause);
55
+ // Shared by the read layer, which answers the caller with a 400, and the drizzle
56
+ // store, which throws at the access callback that built the query.
57
+ export const localizedQuery = ({ kayan, field, what, locales = [], }) => what === 'sort'
58
+ ? `Kayan "${kayan}" field "${field}" is localized, so it is one column per locale and no sort can name it. A sort that names one locale is not built. Name a field that is not localized, or read the rows and pick the locale in the app.`
59
+ : `Kayan "${kayan}" field "${field}" is localized, so it is one column per locale and no filter can name the field itself. Name the locale as a second path segment instead, ${locales.map((locale) => `"${field}.${locale}"`).join(' or ')}. That path is one hop only, so a localized field reached across a relationship cannot name its locale.`;
@@ -0,0 +1,68 @@
1
+ import { type Authorize, type ManagedAdmin, type ReadOptions } from './write.ts';
2
+ import { type Where } from '../../mithaq/where.ts';
3
+ import { type AuditHandler, type ReadTools, type ResolvedOperation } from './steps.ts';
4
+ import type { StandardSchemaV1 } from '../../mithaq/standard-schema.ts';
5
+ import { type ResolvedKayan } from '../../mithaq/types.ts';
6
+ import type { Store } from '../../mithaq/language.ts';
7
+ import { type TenantScope } from './tenant.ts';
8
+ /** What a `find` list takes where it declares no input of its own. */
9
+ export type FindInput = {
10
+ /** How far a read populates its `join` fields: 0 populates nothing, each level through the child kayan's own managed read. */
11
+ depth?: number;
12
+ limit?: number;
13
+ page?: number;
14
+ sort?: string;
15
+ where?: Where;
16
+ };
17
+ export type Paginated<Row> = {
18
+ docs: Row[];
19
+ hasNextPage: boolean;
20
+ hasPrevPage: boolean;
21
+ limit: number;
22
+ nextPage: number | null;
23
+ page: number;
24
+ pagingCounter: number;
25
+ prevPage: number | null;
26
+ totalDocs: number;
27
+ totalPages: number;
28
+ };
29
+ export type ReaderMap<Actor = unknown> = {
30
+ operation: (args: {
31
+ name: string;
32
+ declaration: ResolvedOperation;
33
+ input: StandardSchemaV1;
34
+ output: StandardSchemaV1;
35
+ /** What a draft or a version is read back through, where the kayan keeps drafts or versions. */
36
+ draftOutput?: StandardSchemaV1;
37
+ }) => (data?: Record<string, unknown>, options?: ReadOptions<Actor>) => Promise<Record<string, unknown> | null | Paginated<Record<string, unknown>>>;
38
+ };
39
+ export declare const assertReadableWhere: <Actor>({ kayan, where, actor, authorize, admin, }: {
40
+ kayan: ResolvedKayan;
41
+ where: Where | undefined;
42
+ actor: Actor | null;
43
+ authorize: Authorize;
44
+ admin?: ManagedAdmin<Actor>;
45
+ }) => Promise<Where | undefined>;
46
+ /** The caller's own `where`, refused as a 400 where its grammar is malformed or a value is not of its column's kind. */
47
+ export declare const checkedWhere: (kayan: ResolvedKayan, where: unknown) => Promise<Where>;
48
+ export declare const readers: <Actor>({ kayan, store, cap, defaultLimit, joinLimit, admin, authorize, kayans, of, audit, catalog, openTenant, }: {
49
+ kayan: ResolvedKayan;
50
+ store: Store;
51
+ cap: number;
52
+ /** The service target's page where a caller names none. */
53
+ defaultLimit: number;
54
+ /** The service target's join page where a join field names none. */
55
+ joinLimit: number;
56
+ admin: ManagedAdmin<Actor> | undefined;
57
+ authorize: Authorize;
58
+ kayans: (effect: 'read') => unknown;
59
+ of: ReadTools['of'];
60
+ audit?: AuditHandler<Actor>;
61
+ catalog?: readonly string[];
62
+ /** Reads a read's `tenant` option into the scope every managed call under it keeps. */
63
+ openTenant: (args: {
64
+ actor: unknown;
65
+ reach: unknown;
66
+ kayan: ResolvedKayan;
67
+ }) => Promise<TenantScope | undefined>;
68
+ }) => ReaderMap<Actor>;