rastack 0.0.16 → 0.0.19

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 (105) hide show
  1. package/CHANGELOG.md +43874 -43013
  2. package/dist/.prettierrc +7 -7
  3. package/dist/compile/analyze.js +19 -11
  4. package/dist/compile/index.js +26 -17
  5. package/dist/compile/manifest.js +2 -3
  6. package/dist/compile/openapi.js +5 -8
  7. package/dist/compile/program.js +19 -10
  8. package/dist/csv-schema.js +5 -6
  9. package/dist/define/index.js +2 -2
  10. package/dist/entity-generation/delete-method.js +8 -10
  11. package/dist/entity-generation/form.js +80 -82
  12. package/dist/entity-generation/get-method.js +35 -36
  13. package/dist/entity-generation/imports.js +24 -20
  14. package/dist/entity-generation/sync-method.d.ts +1 -1
  15. package/dist/entity-generation/sync-method.js +45 -46
  16. package/dist/entity-generation/update-method.js +9 -10
  17. package/dist/rad-wasm-build.d.ts +1 -1
  18. package/dist/rad-wasm-build.js +15 -6
  19. package/dist/rad.js +9 -2
  20. package/dist/schema/camel-to-pastel.js +1 -2
  21. package/dist/schema/capitalise-first-letter.js +1 -2
  22. package/dist/schema/extract-response.js +3 -4
  23. package/dist/schema/fetch-schema.js +4 -16
  24. package/dist/schema/remove-non-model-paths.js +1 -2
  25. package/dist/schema/to-camel-case.js +1 -2
  26. package/dist/schema/to-pastel-case.js +1 -2
  27. package/dist/schema-convert.js +22 -33
  28. package/dist/schema-entities.js +177 -189
  29. package/dist/schema-fetch.js +46 -63
  30. package/dist/schema-full.js +23 -36
  31. package/dist/schema-index.js +16 -27
  32. package/dist/schema-params.js +11 -22
  33. package/dist/seed.js +95 -97
  34. package/hooks/form/form.ts +207 -207
  35. package/hooks/form/index.ts +8 -8
  36. package/hooks/form/interfaces.ts +217 -217
  37. package/hooks/form/structure.ts +39 -39
  38. package/hooks/form/validate-schema.ts +49 -49
  39. package/hooks/index.ts +3 -3
  40. package/hooks/query/api.ts +42 -42
  41. package/hooks/query/delete.ts +45 -45
  42. package/hooks/query/fetch.ts +48 -48
  43. package/hooks/query/index.ts +21 -21
  44. package/hooks/query/interfaces.ts +111 -111
  45. package/hooks/query/list.ts +286 -286
  46. package/hooks/query/update.ts +88 -88
  47. package/hooks/query/url.ts +31 -31
  48. package/hooks/real-time/index.ts +1 -1
  49. package/hooks/real-time/pusher.ts +43 -43
  50. package/jest.config.cjs +6 -6
  51. package/package.json +57 -57
  52. package/provider/index.ts +13 -13
  53. package/provider/provider.tsx +187 -187
  54. package/provider/types.ts +114 -114
  55. package/provider/warehouse.ts +167 -167
  56. package/provider/wasm.ts +124 -124
  57. package/runtime.ts +3 -3
  58. package/src/.prettierrc +7 -7
  59. package/src/compile/analyze.ts +224 -224
  60. package/src/compile/index.ts +86 -86
  61. package/src/compile/manifest.ts +10 -10
  62. package/src/compile/model.ts +69 -69
  63. package/src/compile/openapi.ts +266 -266
  64. package/src/compile/program.ts +40 -40
  65. package/src/csv-schema.ts +187 -187
  66. package/src/define/index.ts +162 -162
  67. package/src/entity-generation/delete-method.ts +37 -37
  68. package/src/entity-generation/form.ts +169 -169
  69. package/src/entity-generation/get-method.ts +80 -80
  70. package/src/entity-generation/imports.ts +110 -110
  71. package/src/entity-generation/sync-method.ts +222 -222
  72. package/src/entity-generation/update-method.ts +38 -38
  73. package/src/index.ts +77 -77
  74. package/src/rad-compile.ts +88 -88
  75. package/src/rad-wasm-build.ts +102 -102
  76. package/src/rad.ts +101 -101
  77. package/src/scan.ts +89 -89
  78. package/src/schema/camel-to-pastel.ts +3 -3
  79. package/src/schema/capitalise-first-letter.ts +3 -3
  80. package/src/schema/extract-response.ts +38 -38
  81. package/src/schema/fetch-schema.ts +12 -12
  82. package/src/schema/remove-non-model-paths.ts +13 -13
  83. package/src/schema/to-camel-case.ts +7 -7
  84. package/src/schema/to-pastel-case.ts +6 -6
  85. package/src/schema-convert.ts +61 -61
  86. package/src/schema-entities.ts +401 -401
  87. package/src/schema-fetch.ts +82 -82
  88. package/src/schema-full.ts +38 -38
  89. package/src/schema-index.ts +29 -29
  90. package/src/schema-params.ts +116 -116
  91. package/src/seed.ts +297 -297
  92. package/sync/engine.ts +392 -392
  93. package/sync/hooks.ts +450 -450
  94. package/sync/index.ts +8 -8
  95. package/sync/persistence.ts +237 -237
  96. package/sync/provider.tsx +91 -91
  97. package/sync/registry.ts +36 -36
  98. package/sync/store.ts +126 -126
  99. package/sync/transactions.ts +300 -300
  100. package/sync/types.ts +94 -94
  101. package/test/compile.spec.ts +192 -192
  102. package/test/csv-schema.spec.ts +143 -143
  103. package/test/schema-entities.spec.ts +688 -688
  104. package/tsconfig.json +24 -24
  105. package/types.ts +18 -18
@@ -1,110 +1,110 @@
1
- export function removeDuplicateImports(importStatements: string): string {
2
- const importLines = importStatements.split("\n");
3
- const uniqueImports = new Set<string>();
4
- const radStackImports = new Set<string>();
5
- const radSyncImports = new Set<string>();
6
- const schemaImports = new Set<string>();
7
-
8
- importLines.forEach((line) => {
9
- if (line.includes("rastack/sync")) {
10
- const imports =
11
- line
12
- .match(/\{([^}]+)\}/)?.[1]
13
- .split(",")
14
- .map((s) => s.trim()) || [];
15
- imports.forEach((imp) => radSyncImports.add(imp));
16
- } else if (line.includes("rastack/hooks")) {
17
- const imports =
18
- line
19
- .match(/\{([^}]+)\}/)?.[1]
20
- .split(",")
21
- .map((s) => s.trim()) || [];
22
- imports.forEach((imp) => radStackImports.add(imp));
23
- } else if (line.includes("@repo/schema/schema")) {
24
- const imports =
25
- line
26
- .match(/\{([^}]+)\}/)?.[1]
27
- .split(",")
28
- .map((s) => s.trim()) || [];
29
- imports.forEach((imp) => schemaImports.add(imp));
30
- } else {
31
- uniqueImports.add(line);
32
- }
33
- });
34
-
35
- let result = Array.from(uniqueImports).join("\n") + "\n";
36
-
37
- if (radStackImports.size > 0) {
38
- result += `import { ${Array.from(radStackImports).join(", ")} } from 'rastack/hooks';\n`;
39
- }
40
-
41
- if (radSyncImports.size > 0) {
42
- result += `import { ${Array.from(radSyncImports).join(", ")} } from 'rastack/sync';\n`;
43
- }
44
-
45
- if (schemaImports.size > 0) {
46
- result += `import { ${Array.from(schemaImports).join(", ")} } from '@repo/schema/schema';\n`;
47
- }
48
-
49
- return result;
50
- }
51
-
52
- export function generateImportStatements(
53
- importStatements: Set<string>,
54
- modelName: string,
55
- ): string {
56
- const entries = Array.from(importStatements);
57
- const syncImports = entries
58
- .filter((importStatement) => importStatement.startsWith("sync:"))
59
- .map((importStatement) => importStatement.slice("sync:".length))
60
- .join(", ");
61
- const queryImports = entries
62
- .filter(
63
- (importStatement) =>
64
- !importStatement.startsWith("sync:") && importStatement.includes("use"),
65
- )
66
- .join(", ");
67
- const schemaImports = entries
68
- .filter(
69
- (importStatement) =>
70
- importStatement.startsWith("I") || importStatement.startsWith("Deref"),
71
- )
72
- .join(", ");
73
-
74
- let importData =
75
- "import { useForm, FormProps, FormUpdate, useUpdateQuery } from 'rastack/hooks';\n";
76
- importData = importData + 'import { useAuth0 } from "@auth0/auth0-react";\n';
77
- if (queryImports) {
78
- importData += `import { ${queryImports} } from 'rastack/hooks';\n`;
79
- }
80
- if (syncImports) {
81
- importData += `import { ${syncImports} } from 'rastack/sync';\n`;
82
- }
83
- if (schemaImports) {
84
- importData += `import { ${schemaImports} } from '@repo/schema/schema';\n`;
85
- }
86
-
87
- return removeDuplicateImports(importData);
88
- }
89
-
90
- export function generateKeyImports(model: string, operations: string[]) {
91
- model = model.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
92
- let keyData = `export const ${model}QueriesKeys = {\n`;
93
- operations.forEach((item) => {
94
- const snakeCase = item.replace(/([A-Z])/g, "-$1").toLowerCase();
95
- keyData += ` ${item}: "${snakeCase}",\n`;
96
- });
97
- return `${keyData}}`;
98
- }
99
-
100
- export function generateIndexImports(model: string, operations: string[]) {
101
- return `export * from "./api"`;
102
- }
103
-
104
- export function generateFolderImports(models: string[]) {
105
- let importData = "";
106
- models.map((model) => {
107
- importData += `export * from "./${model}"\n`;
108
- });
109
- return importData;
110
- }
1
+ export function removeDuplicateImports(importStatements: string): string {
2
+ const importLines = importStatements.split("\n");
3
+ const uniqueImports = new Set<string>();
4
+ const radStackImports = new Set<string>();
5
+ const radSyncImports = new Set<string>();
6
+ const schemaImports = new Set<string>();
7
+
8
+ importLines.forEach((line) => {
9
+ if (line.includes("rastack/sync")) {
10
+ const imports =
11
+ line
12
+ .match(/\{([^}]+)\}/)?.[1]
13
+ .split(",")
14
+ .map((s) => s.trim()) || [];
15
+ imports.forEach((imp) => radSyncImports.add(imp));
16
+ } else if (line.includes("rastack/hooks")) {
17
+ const imports =
18
+ line
19
+ .match(/\{([^}]+)\}/)?.[1]
20
+ .split(",")
21
+ .map((s) => s.trim()) || [];
22
+ imports.forEach((imp) => radStackImports.add(imp));
23
+ } else if (line.includes("@repo/schema/schema")) {
24
+ const imports =
25
+ line
26
+ .match(/\{([^}]+)\}/)?.[1]
27
+ .split(",")
28
+ .map((s) => s.trim()) || [];
29
+ imports.forEach((imp) => schemaImports.add(imp));
30
+ } else {
31
+ uniqueImports.add(line);
32
+ }
33
+ });
34
+
35
+ let result = Array.from(uniqueImports).join("\n") + "\n";
36
+
37
+ if (radStackImports.size > 0) {
38
+ result += `import { ${Array.from(radStackImports).join(", ")} } from 'rastack/hooks';\n`;
39
+ }
40
+
41
+ if (radSyncImports.size > 0) {
42
+ result += `import { ${Array.from(radSyncImports).join(", ")} } from 'rastack/sync';\n`;
43
+ }
44
+
45
+ if (schemaImports.size > 0) {
46
+ result += `import { ${Array.from(schemaImports).join(", ")} } from '@repo/schema/schema';\n`;
47
+ }
48
+
49
+ return result;
50
+ }
51
+
52
+ export function generateImportStatements(
53
+ importStatements: Set<string>,
54
+ modelName: string,
55
+ ): string {
56
+ const entries = Array.from(importStatements);
57
+ const syncImports = entries
58
+ .filter((importStatement) => importStatement.startsWith("sync:"))
59
+ .map((importStatement) => importStatement.slice("sync:".length))
60
+ .join(", ");
61
+ const queryImports = entries
62
+ .filter(
63
+ (importStatement) =>
64
+ !importStatement.startsWith("sync:") && importStatement.includes("use"),
65
+ )
66
+ .join(", ");
67
+ const schemaImports = entries
68
+ .filter(
69
+ (importStatement) =>
70
+ importStatement.startsWith("I") || importStatement.startsWith("Deref"),
71
+ )
72
+ .join(", ");
73
+
74
+ let importData =
75
+ "import { useForm, FormProps, FormUpdate, useUpdateQuery } from 'rastack/hooks';\n";
76
+ importData = importData + 'import { useAuth0 } from "@auth0/auth0-react";\n';
77
+ if (queryImports) {
78
+ importData += `import { ${queryImports} } from 'rastack/hooks';\n`;
79
+ }
80
+ if (syncImports) {
81
+ importData += `import { ${syncImports} } from 'rastack/sync';\n`;
82
+ }
83
+ if (schemaImports) {
84
+ importData += `import { ${schemaImports} } from '@repo/schema/schema';\n`;
85
+ }
86
+
87
+ return removeDuplicateImports(importData);
88
+ }
89
+
90
+ export function generateKeyImports(model: string, operations: string[]) {
91
+ model = model.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
92
+ let keyData = `export const ${model}QueriesKeys = {\n`;
93
+ operations.forEach((item) => {
94
+ const snakeCase = item.replace(/([A-Z])/g, "-$1").toLowerCase();
95
+ keyData += ` ${item}: "${snakeCase}",\n`;
96
+ });
97
+ return `${keyData}}`;
98
+ }
99
+
100
+ export function generateIndexImports(model: string, operations: string[]) {
101
+ return `export * from "./api"`;
102
+ }
103
+
104
+ export function generateFolderImports(models: string[]) {
105
+ let importData = "";
106
+ models.map((model) => {
107
+ importData += `export * from "./${model}"\n`;
108
+ });
109
+ return importData;
110
+ }
@@ -1,222 +1,222 @@
1
- import { capitaliseFirstLetter } from "../schema/capitalise-first-letter";
2
- import { toCamelCase } from "../schema/to-camel-case";
3
-
4
- /**
5
- * Generators for sync-mode entities (operations carrying the
6
- * `x-rastack-sync` OpenAPI extension emitted by rastack). Reads bind to the
7
- * local store, writes enqueue optimistic transactions, and operations the
8
- * fat class marked as confirmed become blocking online-only mutations.
9
- *
10
- * Imports prefixed with "sync:" are routed to 'rastack/sync' by
11
- * generateImportStatements.
12
- */
13
-
14
- export interface EntitySyncMeta {
15
- sync: boolean;
16
- /** Sync.load = "lazy": excluded from initial bootstrap, loaded on first use. */
17
- lazy?: boolean;
18
- /** Sync.client_ids = True: rows use client-generated UUID primary keys. */
19
- clientIds?: boolean;
20
- confirmedOperationIds: Set<string>;
21
- listUrl?: string;
22
- detailUrl?: string;
23
- detailParam?: string;
24
- listOperationId?: string;
25
- detailOperationId?: string;
26
- rowSchema?: string; // e.g. "IPet"
27
- }
28
-
29
- export function entityConstName(model: string): string {
30
- return `${toCamelCase(model)}Entity`;
31
- }
32
-
33
- export function generateEntityDescriptor(
34
- appName: string,
35
- modelName: string,
36
- meta: EntitySyncMeta,
37
- ): [string, Set<string>] {
38
- const importStatements = new Set<string>();
39
- importStatements.add("sync:defineEntity");
40
- if (meta.rowSchema) {
41
- importStatements.add(meta.rowSchema);
42
- }
43
- const rowType = meta.rowSchema || "any";
44
- const listUrl =
45
- meta.listUrl ||
46
- (meta.detailUrl ? meta.detailUrl.replace(/\/?\{[^}]+\}.*$/, "") : "");
47
- const detailUrl = meta.detailUrl
48
- ? meta.detailUrl.replace(/\{[^}]+\}/, "${id}")
49
- : `${listUrl.replace(/\/$/, "")}/\${id}`;
50
-
51
- const lazyLine = meta.lazy ? `\n lazy: true,` : "";
52
- const clientIdsLine = meta.clientIds ? `\n clientIds: true,` : "";
53
-
54
- return [
55
- `
56
- export const ${entityConstName(modelName)} = defineEntity<${rowType}>({
57
- app: "${appName}",
58
- model: "${modelName}",
59
- listUrl: \`${listUrl}\`,
60
- detailUrl: (id) => \`${detailUrl}\`,${lazyLine}${clientIdsLine}
61
- });
62
- `,
63
- importStatements,
64
- ];
65
- }
66
-
67
- export function generateSyncListMethod(
68
- modelName: string,
69
- operationId: string,
70
- rowSchema: string | undefined,
71
- ): [string, Set<string>] {
72
- const operationIdUpper = capitaliseFirstLetter(operationId);
73
- const rowType = rowSchema || "any";
74
- const importStatements = new Set<string>();
75
- importStatements.add("sync:useEntityList");
76
- importStatements.add("sync:EntityListOptions");
77
- if (rowSchema) {
78
- importStatements.add(rowSchema);
79
- }
80
- return [
81
- `
82
- export function use${operationIdUpper}(
83
- options?: EntityListOptions<${rowType}>
84
- ) {
85
- return useEntityList<${rowType}>(${entityConstName(modelName)}, options);
86
- }`,
87
- importStatements,
88
- ];
89
- }
90
-
91
- export function generateSyncRecordMethod(
92
- modelName: string,
93
- operationId: string,
94
- rowSchema: string | undefined,
95
- pathParameters: string[],
96
- ): [string, Set<string>] {
97
- const operationIdUpper = capitaliseFirstLetter(operationId);
98
- const rowType = rowSchema || "any";
99
- const paramNames = `I${operationIdUpper}Params`;
100
- const idParam = pathParameters[0];
101
- const importStatements = new Set<string>();
102
- importStatements.add("sync:useEntityRecord");
103
- importStatements.add(`GetQueryOptions, useGetQuery`);
104
- importStatements.add(paramNames);
105
- if (rowSchema) {
106
- importStatements.add(rowSchema);
107
- }
108
- return [
109
- `
110
- export function use${operationIdUpper}(
111
- options?: GetQueryOptions<${paramNames}>
112
- ) {
113
- const id = options?.params?.${idParam};
114
- return useEntityRecord<${rowType}>(${entityConstName(modelName)}, id as string | number | undefined);
115
- }`,
116
- importStatements,
117
- ];
118
- }
119
-
120
- export function generateSyncUpdateMethod(
121
- url: string,
122
- method: string,
123
- modelName: string,
124
- operationId: string,
125
- requestBody: string | undefined,
126
- responseBody: string | undefined,
127
- pathParameters: string[],
128
- ): [string, Set<string>] {
129
- const operationIdUpper = capitaliseFirstLetter(operationId);
130
- const paramNames = `I${operationIdUpper}Params`;
131
- const methodUpper = method.charAt(0).toUpperCase() + method.slice(1);
132
- const isCreate = pathParameters.length === 0;
133
- const idParam = pathParameters[0];
134
-
135
- let urlStructure = url.replace(/{([^}]+)}/g, "${params.$1}");
136
- urlStructure = `(params) => \`${urlStructure}\``;
137
-
138
- const importStatements = new Set<string>();
139
- importStatements.add("sync:useEntityMutation");
140
- importStatements.add(paramNames);
141
- if (requestBody) {
142
- importStatements.add(requestBody);
143
- }
144
- if (responseBody) {
145
- importStatements.add(responseBody);
146
- }
147
-
148
- const idLine = isCreate
149
- ? ""
150
- : `\n id: (params) => params.${idParam} as string | number | undefined,`;
151
-
152
- return [
153
- `
154
- export function use${methodUpper}${operationIdUpper}() {
155
- return useEntityMutation<${paramNames}, ${requestBody || "any"}, ${responseBody || "any"}>(${entityConstName(modelName)}, {
156
- op: "${isCreate ? "create" : "update"}",
157
- method: "${method}",
158
- url: ${urlStructure},${idLine}
159
- });
160
- }`,
161
- importStatements,
162
- ];
163
- }
164
-
165
- export function generateSyncDeleteMethod(
166
- url: string,
167
- modelName: string,
168
- operationId: string,
169
- ): [string, Set<string>] {
170
- const operationIdUpper = capitaliseFirstLetter(operationId);
171
- const urlStructure = url.replace(/{[^}]+}/, "${id}");
172
- const importStatements = new Set<string>();
173
- importStatements.add("sync:useEntityDelete");
174
-
175
- return [
176
- `
177
- export function use${operationIdUpper}() {
178
- return useEntityDelete(${entityConstName(modelName)}, {
179
- url: (id) => \`${urlStructure}\`,
180
- });
181
- }
182
- `,
183
- importStatements,
184
- ];
185
- }
186
-
187
- export function generateConfirmedMethod(
188
- url: string,
189
- method: string,
190
- operationId: string,
191
- requestBody: string | undefined,
192
- responseBody: string | undefined,
193
- ): [string, Set<string>] {
194
- const operationIdUpper = capitaliseFirstLetter(operationId);
195
- const paramNames = `I${operationIdUpper}Params`;
196
- const methodUpper = method.charAt(0).toUpperCase() + method.slice(1);
197
-
198
- let urlStructure = url.replace(/{([^}]+)}/g, "${params.$1}");
199
- urlStructure = `(params) => \`${urlStructure}\``;
200
-
201
- const importStatements = new Set<string>();
202
- importStatements.add("sync:useConfirmedMutation");
203
- importStatements.add(paramNames);
204
- if (requestBody) {
205
- importStatements.add(requestBody);
206
- }
207
- if (responseBody) {
208
- importStatements.add(responseBody);
209
- }
210
-
211
- return [
212
- `
213
- // Confirmed ("sync") operation: blocking, online-only, no optimistic apply.
214
- export function use${methodUpper}${operationIdUpper}() {
215
- return useConfirmedMutation<${paramNames}, ${requestBody || "any"}, ${responseBody || "any"}>({
216
- method: "${method}",
217
- url: ${urlStructure},
218
- });
219
- }`,
220
- importStatements,
221
- ];
222
- }
1
+ import { capitaliseFirstLetter } from "../schema/capitalise-first-letter";
2
+ import { toCamelCase } from "../schema/to-camel-case";
3
+
4
+ /**
5
+ * Generators for sync-mode entities (operations carrying the
6
+ * `x-rastack-sync` OpenAPI extension emitted by rastack). Reads bind to the
7
+ * local store, writes enqueue optimistic transactions, and operations the
8
+ * fat class marked as confirmed become blocking online-only mutations.
9
+ *
10
+ * Imports prefixed with "sync:" are routed to 'rastack/sync' by
11
+ * generateImportStatements.
12
+ */
13
+
14
+ export interface EntitySyncMeta {
15
+ sync: boolean;
16
+ /** Sync.load = "lazy": excluded from initial bootstrap, loaded on first use. */
17
+ lazy?: boolean;
18
+ /** Sync.client_ids = True: rows use client-generated UUID primary keys. */
19
+ clientIds?: boolean;
20
+ confirmedOperationIds: Set<string>;
21
+ listUrl?: string;
22
+ detailUrl?: string;
23
+ detailParam?: string;
24
+ listOperationId?: string;
25
+ detailOperationId?: string;
26
+ rowSchema?: string; // e.g. "IPet"
27
+ }
28
+
29
+ export function entityConstName(model: string): string {
30
+ return `${toCamelCase(model)}Entity`;
31
+ }
32
+
33
+ export function generateEntityDescriptor(
34
+ appName: string,
35
+ modelName: string,
36
+ meta: EntitySyncMeta,
37
+ ): [string, Set<string>] {
38
+ const importStatements = new Set<string>();
39
+ importStatements.add("sync:defineEntity");
40
+ if (meta.rowSchema) {
41
+ importStatements.add(meta.rowSchema);
42
+ }
43
+ const rowType = meta.rowSchema || "any";
44
+ const listUrl =
45
+ meta.listUrl ||
46
+ (meta.detailUrl ? meta.detailUrl.replace(/\/?\{[^}]+\}.*$/, "") : "");
47
+ const detailUrl = meta.detailUrl
48
+ ? meta.detailUrl.replace(/\{[^}]+\}/, "${id}")
49
+ : `${listUrl.replace(/\/$/, "")}/\${id}`;
50
+
51
+ const lazyLine = meta.lazy ? `\n lazy: true,` : "";
52
+ const clientIdsLine = meta.clientIds ? `\n clientIds: true,` : "";
53
+
54
+ return [
55
+ `
56
+ export const ${entityConstName(modelName)} = defineEntity<${rowType}>({
57
+ app: "${appName}",
58
+ model: "${modelName}",
59
+ listUrl: \`${listUrl}\`,
60
+ detailUrl: (id) => \`${detailUrl}\`,${lazyLine}${clientIdsLine}
61
+ });
62
+ `,
63
+ importStatements,
64
+ ];
65
+ }
66
+
67
+ export function generateSyncListMethod(
68
+ modelName: string,
69
+ operationId: string,
70
+ rowSchema: string | undefined,
71
+ ): [string, Set<string>] {
72
+ const operationIdUpper = capitaliseFirstLetter(operationId);
73
+ const rowType = rowSchema || "any";
74
+ const importStatements = new Set<string>();
75
+ importStatements.add("sync:useEntityList");
76
+ importStatements.add("sync:EntityListOptions");
77
+ if (rowSchema) {
78
+ importStatements.add(rowSchema);
79
+ }
80
+ return [
81
+ `
82
+ export function use${operationIdUpper}(
83
+ options?: EntityListOptions<${rowType}>
84
+ ) {
85
+ return useEntityList<${rowType}>(${entityConstName(modelName)}, options);
86
+ }`,
87
+ importStatements,
88
+ ];
89
+ }
90
+
91
+ export function generateSyncRecordMethod(
92
+ modelName: string,
93
+ operationId: string,
94
+ rowSchema: string | undefined,
95
+ pathParameters: string[],
96
+ ): [string, Set<string>] {
97
+ const operationIdUpper = capitaliseFirstLetter(operationId);
98
+ const rowType = rowSchema || "any";
99
+ const paramNames = `I${operationIdUpper}Params`;
100
+ const idParam = pathParameters[0];
101
+ const importStatements = new Set<string>();
102
+ importStatements.add("sync:useEntityRecord");
103
+ importStatements.add(`GetQueryOptions, useGetQuery`);
104
+ importStatements.add(paramNames);
105
+ if (rowSchema) {
106
+ importStatements.add(rowSchema);
107
+ }
108
+ return [
109
+ `
110
+ export function use${operationIdUpper}(
111
+ options?: GetQueryOptions<${paramNames}>
112
+ ) {
113
+ const id = options?.params?.${idParam};
114
+ return useEntityRecord<${rowType}>(${entityConstName(modelName)}, id as string | number | undefined);
115
+ }`,
116
+ importStatements,
117
+ ];
118
+ }
119
+
120
+ export function generateSyncUpdateMethod(
121
+ url: string,
122
+ method: string,
123
+ modelName: string,
124
+ operationId: string,
125
+ requestBody: string | undefined,
126
+ responseBody: string | undefined,
127
+ pathParameters: string[],
128
+ ): [string, Set<string>] {
129
+ const operationIdUpper = capitaliseFirstLetter(operationId);
130
+ const paramNames = `I${operationIdUpper}Params`;
131
+ const methodUpper = method.charAt(0).toUpperCase() + method.slice(1);
132
+ const isCreate = pathParameters.length === 0;
133
+ const idParam = pathParameters[0];
134
+
135
+ let urlStructure = url.replace(/{([^}]+)}/g, "${params.$1}");
136
+ urlStructure = `(params) => \`${urlStructure}\``;
137
+
138
+ const importStatements = new Set<string>();
139
+ importStatements.add("sync:useEntityMutation");
140
+ importStatements.add(paramNames);
141
+ if (requestBody) {
142
+ importStatements.add(requestBody);
143
+ }
144
+ if (responseBody) {
145
+ importStatements.add(responseBody);
146
+ }
147
+
148
+ const idLine = isCreate
149
+ ? ""
150
+ : `\n id: (params) => params.${idParam} as string | number | undefined,`;
151
+
152
+ return [
153
+ `
154
+ export function use${methodUpper}${operationIdUpper}() {
155
+ return useEntityMutation<${paramNames}, ${requestBody || "any"}, ${responseBody || "any"}>(${entityConstName(modelName)}, {
156
+ op: "${isCreate ? "create" : "update"}",
157
+ method: "${method}",
158
+ url: ${urlStructure},${idLine}
159
+ });
160
+ }`,
161
+ importStatements,
162
+ ];
163
+ }
164
+
165
+ export function generateSyncDeleteMethod(
166
+ url: string,
167
+ modelName: string,
168
+ operationId: string,
169
+ ): [string, Set<string>] {
170
+ const operationIdUpper = capitaliseFirstLetter(operationId);
171
+ const urlStructure = url.replace(/{[^}]+}/, "${id}");
172
+ const importStatements = new Set<string>();
173
+ importStatements.add("sync:useEntityDelete");
174
+
175
+ return [
176
+ `
177
+ export function use${operationIdUpper}() {
178
+ return useEntityDelete(${entityConstName(modelName)}, {
179
+ url: (id) => \`${urlStructure}\`,
180
+ });
181
+ }
182
+ `,
183
+ importStatements,
184
+ ];
185
+ }
186
+
187
+ export function generateConfirmedMethod(
188
+ url: string,
189
+ method: string,
190
+ operationId: string,
191
+ requestBody: string | undefined,
192
+ responseBody: string | undefined,
193
+ ): [string, Set<string>] {
194
+ const operationIdUpper = capitaliseFirstLetter(operationId);
195
+ const paramNames = `I${operationIdUpper}Params`;
196
+ const methodUpper = method.charAt(0).toUpperCase() + method.slice(1);
197
+
198
+ let urlStructure = url.replace(/{([^}]+)}/g, "${params.$1}");
199
+ urlStructure = `(params) => \`${urlStructure}\``;
200
+
201
+ const importStatements = new Set<string>();
202
+ importStatements.add("sync:useConfirmedMutation");
203
+ importStatements.add(paramNames);
204
+ if (requestBody) {
205
+ importStatements.add(requestBody);
206
+ }
207
+ if (responseBody) {
208
+ importStatements.add(responseBody);
209
+ }
210
+
211
+ return [
212
+ `
213
+ // Confirmed ("sync") operation: blocking, online-only, no optimistic apply.
214
+ export function use${methodUpper}${operationIdUpper}() {
215
+ return useConfirmedMutation<${paramNames}, ${requestBody || "any"}, ${responseBody || "any"}>({
216
+ method: "${method}",
217
+ url: ${urlStructure},
218
+ });
219
+ }`,
220
+ importStatements,
221
+ ];
222
+ }