@akanjs/language 0.0.0-stage → 3.1.2-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js ADDED
@@ -0,0 +1,1034 @@
1
+ // pkgs/@akanjs/language/ast.ts
2
+ var createAst = (ts) => {
3
+ const sourceFileFor = (fileName, content, scriptKind = ts.ScriptKind.TS) => ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true, scriptKind);
4
+ const nodeName = (node) => {
5
+ if (!node)
6
+ return null;
7
+ if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node))
8
+ return node.text;
9
+ return null;
10
+ };
11
+ const propertyName = (node) => ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node) || ts.isMethodDeclaration(node) ? nodeName(node.name) : null;
12
+ const expressionName = (expression) => {
13
+ if (ts.isIdentifier(expression))
14
+ return expression.text;
15
+ if (ts.isPropertyAccessExpression(expression))
16
+ return expression.name.text;
17
+ if (ts.isCallExpression(expression))
18
+ return expressionName(expression.expression);
19
+ if (ts.isAsExpression(expression))
20
+ return expressionName(expression.expression);
21
+ return null;
22
+ };
23
+ const callExpressionName = (node) => ts.isPropertyAccessExpression(node.expression) ? node.expression.name.text : expressionName(node.expression);
24
+ const firstObjectReturnedByArrow = (node) => {
25
+ if (!ts.isArrowFunction(node) && !ts.isFunctionExpression(node))
26
+ return null;
27
+ if (ts.isObjectLiteralExpression(node.body))
28
+ return node.body;
29
+ if (ts.isParenthesizedExpression(node.body) && ts.isObjectLiteralExpression(node.body.expression)) {
30
+ return node.body.expression;
31
+ }
32
+ if (!ts.isBlock(node.body))
33
+ return null;
34
+ for (const statement of node.body.statements) {
35
+ if (ts.isReturnStatement(statement) && statement.expression && ts.isObjectLiteralExpression(statement.expression)) {
36
+ return statement.expression;
37
+ }
38
+ }
39
+ return null;
40
+ };
41
+ const heritageCallNamed = (node, names) => {
42
+ const heritage = node.heritageClauses?.flatMap((clause) => [...clause.types]) ?? [];
43
+ for (const { expression } of heritage) {
44
+ if (!ts.isCallExpression(expression))
45
+ continue;
46
+ const callee = expressionName(expression.expression);
47
+ if (callee && names.has(callee))
48
+ return { callee, call: expression };
49
+ }
50
+ return null;
51
+ };
52
+ const viaNames = new Set(["via"]);
53
+ const heritageCall = (node) => heritageCallNamed(node, viaNames)?.call ?? null;
54
+ return {
55
+ sourceFileFor,
56
+ nodeName,
57
+ propertyName,
58
+ expressionName,
59
+ callExpressionName,
60
+ firstObjectReturnedByArrow,
61
+ heritageCallNamed,
62
+ heritageCall
63
+ };
64
+ };
65
+
66
+ // pkgs/@akanjs/language/moduleFacts.ts
67
+ var moduleCallees = new Set(["via", "from", "into", "serve", "slice", "endpoint", "store"]);
68
+ var argMethods = new Set(["param", "body", "search", "room", "msg"]);
69
+ var ownerOf = (relativePath) => {
70
+ const [root, name] = relativePath.split("/");
71
+ return (root === "apps" || root === "libs") && name ? `${root}/${name}` : null;
72
+ };
73
+ var createFactReader = (ts, ast) => {
74
+ const spanOf = (source, node) => {
75
+ const start = node.getStart(source);
76
+ const end = node.getEnd();
77
+ const head = source.getLineAndCharacterOfPosition(start);
78
+ const tail = source.getLineAndCharacterOfPosition(end);
79
+ return {
80
+ fileName: source.fileName,
81
+ start,
82
+ end,
83
+ line: head.line,
84
+ character: head.character,
85
+ endLine: tail.line,
86
+ endCharacter: tail.character
87
+ };
88
+ };
89
+ const unwrap = (expression) => {
90
+ let current = expression;
91
+ while (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isSatisfiesExpression(current))
92
+ current = current.expression;
93
+ return current;
94
+ };
95
+ const moduleRefOf = (expression, holder) => {
96
+ let current = expression && unwrap(expression);
97
+ while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression))
98
+ current = current.expression.expression;
99
+ if (current && ts.isStringLiteral(current))
100
+ return current.text;
101
+ if (!current || !ts.isPropertyAccessExpression(current))
102
+ return null;
103
+ return ts.isIdentifier(current.expression) && current.expression.text === holder ? current.name.text : null;
104
+ };
105
+ const returnedObject = (builder) => builder ? ast.firstObjectReturnedByArrow(builder) : null;
106
+ const builderOf = (call) => [...call.arguments].reverse().find((arg) => ts.isArrowFunction(arg) || ts.isFunctionExpression(arg));
107
+ const guardsIn = (object) => {
108
+ const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
109
+ if (!guards)
110
+ return [];
111
+ if (ts.isArrayLiteralExpression(guards))
112
+ return guards.elements.map((element) => ast.expressionName(element)).filter((name) => name !== null);
113
+ if (ts.isObjectLiteralExpression(guards))
114
+ return guards.properties.filter(ts.isPropertyAssignment).map((property) => `${ast.nodeName(property.name)} ${ast.expressionName(property.initializer) ?? "?"}`);
115
+ const single = ast.expressionName(guards);
116
+ return single ? [single] : [];
117
+ };
118
+ const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
119
+ const chainOf = (initializer) => {
120
+ let root = unwrap(initializer);
121
+ const methods = [];
122
+ while (ts.isCallExpression(root) && ts.isPropertyAccessExpression(root.expression) && ts.isCallExpression(unwrap(root.expression.expression))) {
123
+ methods.push(root.expression.name.text);
124
+ root = unwrap(root.expression.expression);
125
+ }
126
+ return { root: ts.isCallExpression(root) ? root : null, methods };
127
+ };
128
+ const keyName = (property) => ts.isPropertyAssignment(property) || ts.isMethodDeclaration(property) || ts.isShorthandPropertyAssignment(property) ? property.name : null;
129
+ const objectKeys = (source, object) => (object?.properties ?? []).flatMap((property) => {
130
+ const name = ast.propertyName(property);
131
+ const node = keyName(property);
132
+ return name && node ? [{ name, span: spanOf(source, node) }] : [];
133
+ });
134
+ const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
135
+ if (!ts.isPropertyAssignment(property))
136
+ return [];
137
+ const name = ast.nodeName(property.name);
138
+ if (!name)
139
+ return [];
140
+ const { root, methods } = chainOf(property.initializer);
141
+ return [
142
+ {
143
+ name,
144
+ span: spanOf(source, property.name),
145
+ guards: root ? guardsIn(optionObjectOf(root)) : [],
146
+ live: methods.includes("live"),
147
+ argCount: methods.filter((method) => argMethods.has(method)).length
148
+ }
149
+ ];
150
+ });
151
+ const verbReaderOf = (parameter) => {
152
+ const verbs = new Map;
153
+ if (parameter && ts.isObjectBindingPattern(parameter.name))
154
+ for (const element of parameter.name.elements) {
155
+ const verb = element.propertyName ?? element.name;
156
+ if (ts.isIdentifier(verb) && ts.isIdentifier(element.name))
157
+ verbs.set(element.name.text, verb.text);
158
+ }
159
+ const holder = parameter && ts.isIdentifier(parameter.name) ? parameter.name.text : null;
160
+ return (callee) => {
161
+ if (callee && ts.isIdentifier(callee))
162
+ return verbs.get(callee.text) ?? null;
163
+ if (callee && ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && callee.expression.text === holder)
164
+ return callee.name.text;
165
+ return null;
166
+ };
167
+ };
168
+ const endpointKeysOf = (source, builder) => {
169
+ const verbOf = verbReaderOf(builder?.parameters[0]);
170
+ return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
171
+ if (!ts.isPropertyAssignment(property))
172
+ return [];
173
+ const name = ast.nodeName(property.name);
174
+ if (!name)
175
+ return [];
176
+ const { root, methods } = chainOf(property.initializer);
177
+ return [
178
+ {
179
+ name,
180
+ span: spanOf(source, property.name),
181
+ verb: root ? verbOf(root.expression) : null,
182
+ guards: root ? guardsIn(optionObjectOf(root)) : [],
183
+ argCount: methods.filter((method) => argMethods.has(method)).length
184
+ }
185
+ ];
186
+ });
187
+ };
188
+ const filterQueriesOf = (source, builder) => {
189
+ const query = returnedObject(builder)?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "query")?.initializer;
190
+ return query && ts.isObjectLiteralExpression(query) ? objectKeys(source, query) : [];
191
+ };
192
+ const constantOf = (source, name, call) => {
193
+ const parents = call.arguments.map(unwrap).filter((arg) => ts.isIdentifier(arg) || ts.isPropertyAccessExpression(arg)).map((arg) => ast.expressionName(arg)).filter((parent) => parent !== null);
194
+ const fields = call.arguments.flatMap((arg) => objectKeys(source, ast.firstObjectReturnedByArrow(arg)));
195
+ return { ...name, parents, fields };
196
+ };
197
+ const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
198
+ const read = (source, owner) => {
199
+ const facts = {
200
+ fileName: source.fileName,
201
+ owner,
202
+ constants: [],
203
+ filters: [],
204
+ models: [],
205
+ services: [],
206
+ slices: [],
207
+ endpoints: [],
208
+ stores: []
209
+ };
210
+ for (const statement of source.statements) {
211
+ if (!ts.isClassDeclaration(statement) || !statement.name)
212
+ continue;
213
+ const heritage = ast.heritageCallNamed(statement, moduleCallees);
214
+ if (!heritage)
215
+ continue;
216
+ const { callee, call } = heritage;
217
+ const name = { name: statement.name.text, span: spanOf(source, statement.name) };
218
+ const builder = builderOf(call);
219
+ if (callee === "via")
220
+ facts.constants.push(constantOf(source, name, call));
221
+ else if (callee === "from")
222
+ facts.filters.push({ ...name, queries: filterQueriesOf(source, builder) });
223
+ else if (callee === "into")
224
+ facts.models.push({ ...name, model: name.name.endsWith("Model") ? lowerFirst(name.name.slice(0, -5)) : null });
225
+ else if (callee === "serve")
226
+ facts.services.push({ ...name, model: moduleRefOf(call.arguments[0], "db") });
227
+ else if (callee === "slice")
228
+ facts.slices.push({
229
+ ...name,
230
+ model: moduleRefOf(call.arguments[0], "srv"),
231
+ guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
232
+ keys: sliceKeysOf(source, builder)
233
+ });
234
+ else if (callee === "endpoint")
235
+ facts.endpoints.push({
236
+ ...name,
237
+ model: moduleRefOf(call.arguments[0], "srv"),
238
+ keys: endpointKeysOf(source, builder)
239
+ });
240
+ else if (callee === "store")
241
+ facts.stores.push({
242
+ ...name,
243
+ model: moduleRefOf(call.arguments[0], "sig"),
244
+ stateKeys: objectKeys(source, returnedObject(builder))
245
+ });
246
+ }
247
+ return facts;
248
+ };
249
+ return { read, spanOf, moduleRefOf, chainOf, unwrap };
250
+ };
251
+
252
+ // pkgs/akanjs/common/capitalize.ts
253
+ var capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
254
+ var lowerlize = (str) => str.charAt(0).toLowerCase() + str.slice(1);
255
+
256
+ // pkgs/akanjs/common/akanNames.ts
257
+ var sliceNames = (refName, suffix = "") => {
258
+ const [Model, Suffix] = [capitalize(refName), capitalize(suffix)];
259
+ const live = `${refName}Live${Suffix}`;
260
+ return {
261
+ slice: `${refName}${Suffix}`,
262
+ state: {
263
+ defaultModel: `default${Model}${Suffix}`,
264
+ modelInitList: `${refName}InitList${Suffix}`,
265
+ modelInsight: `${refName}Insight${Suffix}`,
266
+ modelList: `${refName}List${Suffix}`,
267
+ modelListLoading: `${refName}ListLoading${Suffix}`,
268
+ modelInitAt: `${refName}InitAt${Suffix}`,
269
+ modelStaleAt: `${refName}StaleAt${Suffix}`,
270
+ lastPageOfModel: `lastPageOf${Model}${Suffix}`,
271
+ pageOfModel: `pageOf${Model}${Suffix}`,
272
+ limitOfModel: `limitOf${Model}${Suffix}`,
273
+ hasMoreOfModel: `hasMoreOf${Model}${Suffix}`,
274
+ isCumulativeOfModel: `isCumulativeOf${Model}${Suffix}`,
275
+ hasInsightOfModel: `hasInsightOf${Model}${Suffix}`,
276
+ queryArgsOfModel: `queryArgsOf${Model}${Suffix}`,
277
+ sortOfModel: `sortOf${Model}${Suffix}`,
278
+ modelSelection: `${refName}Selection${Suffix}`
279
+ },
280
+ action: {
281
+ initModel: `init${Model}${Suffix}`,
282
+ refreshModel: `refresh${Model}${Suffix}`,
283
+ selectModel: `select${Model}${Suffix}`,
284
+ setPageOfModel: `setPageOf${Model}${Suffix}`,
285
+ loadMoreOfModel: `loadMoreOf${Model}${Suffix}`,
286
+ setLimitOfModel: `setLimitOf${Model}${Suffix}`,
287
+ setQueryArgsOfModel: `setQueryArgsOf${Model}${Suffix}`,
288
+ setSortOfModel: `setSortOf${Model}${Suffix}`,
289
+ applyLiveModel: `applyLive${Model}${Suffix}`,
290
+ watchLiveModel: `watchLive${Model}${Suffix}`
291
+ },
292
+ endpoint: {
293
+ list: `${refName}List${Suffix}`,
294
+ insight: `${refName}Insight${Suffix}`,
295
+ live,
296
+ subscribeLive: endpointNames(live).subscribe,
297
+ init: `init${Model}${Suffix}`,
298
+ getInit: `get${Model}Init${Suffix}`,
299
+ initResult: `${refName}Init${Suffix}`
300
+ }
301
+ };
302
+ };
303
+ var queryNames = (queryKey) => {
304
+ const Key = capitalize(queryKey);
305
+ return {
306
+ list: `list${Key}`,
307
+ listIds: `listIds${Key}`,
308
+ find: `find${Key}`,
309
+ findId: `findId${Key}`,
310
+ pick: `pick${Key}`,
311
+ pickId: `pickId${Key}`,
312
+ exists: `exists${Key}`,
313
+ count: `count${Key}`,
314
+ insight: `insight${Key}`,
315
+ query: `query${Key}`,
316
+ remove: `remove${Key}`,
317
+ removeOne: `removeOne${Key}`,
318
+ purge: `purge${Key}`,
319
+ update: `update${Key}`,
320
+ updateOne: `updateOne${Key}`
321
+ };
322
+ };
323
+ var databaseStateNames = (refName) => {
324
+ const className = capitalize(refName);
325
+ return {
326
+ model: refName,
327
+ modelLoading: `${refName}Loading`,
328
+ modelForm: `${refName}Form`,
329
+ modelFormLoading: `${refName}FormLoading`,
330
+ modelFormUploading: `${refName}FormUploading`,
331
+ modelSubmit: `${refName}Submit`,
332
+ modelViewAt: `${refName}ViewAt`,
333
+ modelModal: `${refName}Modal`,
334
+ modelDraft: `${refName}FormDraft`,
335
+ modelOperation: `${refName}Operation`,
336
+ defaultModel: `default${className}`
337
+ };
338
+ };
339
+ var modelNames = (refName) => {
340
+ const Model = capitalize(refName);
341
+ return {
342
+ ...databaseStateNames(refName),
343
+ getModel: `get${Model}`,
344
+ loadModel: `load${Model}`,
345
+ loadModelMany: `load${Model}Many`,
346
+ createModel: `create${Model}`,
347
+ createModelMany: `create${Model}Many`,
348
+ updateModel: `update${Model}`,
349
+ removeModel: `remove${Model}`,
350
+ modelModel: `${refName}Model`,
351
+ modelLoader: `${refName}Loader`,
352
+ modelCache: `${refName}Cache`,
353
+ modelId: `${refName}Id`,
354
+ lightModel: `light${Model}`,
355
+ viewModel: `view${Model}`,
356
+ getModelView: `get${Model}View`,
357
+ modelView: `${refName}View`,
358
+ editModel: `edit${Model}`,
359
+ getModelEdit: `get${Model}Edit`,
360
+ modelEdit: `${refName}Edit`,
361
+ modelObj: `${refName}Obj`,
362
+ modelObjList: `${refName}ObjList`,
363
+ modelObjInsight: `${refName}ObjInsight`,
364
+ addModelFiles: `add${Model}Files`,
365
+ mergeModel: `merge${Model}`,
366
+ createModelInForm: `create${Model}InForm`,
367
+ updateModelInForm: `update${Model}InForm`,
368
+ checkModelSubmitable: `check${Model}Submitable`,
369
+ submitModel: `submit${Model}`,
370
+ newModel: `new${Model}`,
371
+ setModel: `set${Model}`,
372
+ resetModel: `reset${Model}`,
373
+ invalidateModel: `invalidate${Model}`,
374
+ loadModelFormDraft: `load${Model}FormDraft`,
375
+ restoreModelFormDraft: `restore${Model}FormDraft`,
376
+ discardModelFormDraft: `discard${Model}FormDraft`,
377
+ writeOnModel: `writeOn${Model}`,
378
+ setViewOfModel: `setViewOf${Model}`,
379
+ exportCsvOfModel: `exportCsvOf${Model}`,
380
+ exportJsonOfModel: `exportJsonOf${Model}`
381
+ };
382
+ };
383
+ var moduleNames = (refName) => {
384
+ const Model = capitalize(refName);
385
+ return {
386
+ service: `${refName}Service`,
387
+ ServiceClass: `${Model}Service`,
388
+ SliceClass: `${Model}Slice`,
389
+ ModelClass: `${Model}Model`,
390
+ FilterClass: `${Model}Filter`,
391
+ SignalClass: `${Model}Signal`,
392
+ InternalClass: `${Model}Internal`,
393
+ EndpointClass: `${Model}Endpoint`
394
+ };
395
+ };
396
+ var formSetterNames = (className, key) => {
397
+ const classKeyName = capitalize(key);
398
+ return {
399
+ field: lowerlize(key),
400
+ Field: classKeyName,
401
+ setFieldOnModel: `set${classKeyName}On${className}`,
402
+ addFieldOnModel: `add${classKeyName}On${className}`,
403
+ subFieldOnModel: `sub${classKeyName}On${className}`,
404
+ addOrSubFieldOnModel: `addOrSub${classKeyName}On${className}`,
405
+ moveFieldOnModel: `move${classKeyName}On${className}`,
406
+ uploadFieldOnModel: `upload${classKeyName}On${className}`,
407
+ postSetField: `_postSet${classKeyName}`
408
+ };
409
+ };
410
+ var stateSetterName = (key) => `set${capitalize(key)}`;
411
+ var endpointNames = (key) => {
412
+ const Key = capitalize(key);
413
+ return { subscribe: `subscribe${Key}`, listen: `listen${Key}` };
414
+ };
415
+
416
+ // pkgs/@akanjs/language/nameTable.ts
417
+ var capitalize2 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
418
+
419
+ class NameTable {
420
+ #byName = new Map;
421
+ #byFile = new Map;
422
+ constructor(files) {
423
+ const all = [...files];
424
+ const constantsByOwner = new Map;
425
+ for (const facts of all) {
426
+ const constants = constantsByOwner.get(facts.owner) ?? [];
427
+ constants.push(...facts.constants);
428
+ constantsByOwner.set(facts.owner, constants);
429
+ }
430
+ const libConstants = [...constantsByOwner].filter(([owner]) => owner?.startsWith("libs/"));
431
+ const scope = (owner) => ({
432
+ own: constantsByOwner.get(owner) ?? [],
433
+ libs: libConstants.filter(([key]) => key !== owner).flatMap(([, constants]) => constants)
434
+ });
435
+ const scopes = new Map;
436
+ for (const group of NameTable.#groupsOf(all)) {
437
+ const constants = scopes.get(group.owner) ?? scope(group.owner);
438
+ scopes.set(group.owner, constants);
439
+ this.#addGroup(group, constants);
440
+ }
441
+ }
442
+ lookup(name) {
443
+ return this.#byName.get(name) ?? [];
444
+ }
445
+ entriesTargeting(fileName) {
446
+ return this.#byFile.get(fileName) ?? [];
447
+ }
448
+ static #groupsOf(files) {
449
+ const groups = new Map;
450
+ const groupOf = (owner, refName) => {
451
+ const key = `${owner ?? ""}:${refName}`;
452
+ const group = groups.get(key) ?? { owner, refName, constants: [], slices: [], facts: [] };
453
+ groups.set(key, group);
454
+ return group;
455
+ };
456
+ for (const facts of files) {
457
+ const refs = new Set;
458
+ for (const item of [...facts.services, ...facts.slices, ...facts.endpoints, ...facts.stores, ...facts.models])
459
+ if (item.model)
460
+ refs.add(item.model);
461
+ for (const filter of facts.filters)
462
+ if (filter.name.endsWith("Filter"))
463
+ refs.add(NameTable.#lowerFirst(filter.name.slice(0, -6)));
464
+ for (const constant of facts.constants)
465
+ refs.add(NameTable.#lowerFirst(constant.name));
466
+ for (const refName of refs)
467
+ groupOf(facts.owner, refName).facts.push(facts);
468
+ }
469
+ for (const group of groups.values()) {
470
+ group.constants = group.facts.flatMap((facts) => facts.constants);
471
+ group.slices = group.facts.flatMap((facts) => facts.slices.filter((slice) => slice.model === group.refName));
472
+ }
473
+ return [...groups.values()].filter((group) => group.facts.some((facts) => facts.services.some((item) => item.model === group.refName) || facts.slices.some((item) => item.model === group.refName) || facts.stores.some((item) => item.model === group.refName) || facts.models.some((item) => item.model === group.refName)));
474
+ }
475
+ static #lowerFirst(text) {
476
+ return text.charAt(0).toLowerCase() + text.slice(1);
477
+ }
478
+ #add(entry) {
479
+ if (entry.name === entry.sourceKey)
480
+ return;
481
+ const sameName = this.#byName.get(entry.name) ?? [];
482
+ if (sameName.some((other) => other.gate === entry.gate && other.span.fileName === entry.span.fileName && other.span.start === entry.span.start))
483
+ return;
484
+ sameName.push(entry);
485
+ this.#byName.set(entry.name, sameName);
486
+ const sameFile = this.#byFile.get(entry.span.fileName) ?? [];
487
+ sameFile.push(entry);
488
+ this.#byFile.set(entry.span.fileName, sameFile);
489
+ }
490
+ #addGroup(group, constants) {
491
+ const { owner, refName } = group;
492
+ const Model = capitalize2(refName);
493
+ const base = { owner, refName };
494
+ const modelClass = group.constants.find((constant) => constant.name === Model);
495
+ const facts = group.facts;
496
+ const names = moduleNames(refName);
497
+ const serviceClass = facts.flatMap((file) => file.services).find((item) => item.model === refName);
498
+ const sliceClass = group.slices[0];
499
+ const modelDoc = facts.flatMap((file) => file.models).find((item) => item.model === refName);
500
+ const filterClass = facts.flatMap((file) => file.filters).find((item) => item.name === names.FilterClass);
501
+ if (serviceClass) {
502
+ const target = { ...base, family: "module", sourceKey: serviceClass.name, span: serviceClass.span };
503
+ this.#add({ ...target, name: names.service, role: "service", gate: null, label: serviceClass.name, detail: [] });
504
+ this.#add({ ...target, name: refName, role: "srv", gate: "srv", label: serviceClass.name, detail: [] });
505
+ }
506
+ if (sliceClass)
507
+ this.#add({
508
+ ...base,
509
+ family: "module",
510
+ sourceKey: sliceClass.name,
511
+ span: sliceClass.span,
512
+ name: refName,
513
+ role: "sig",
514
+ gate: "sig",
515
+ label: sliceClass.name,
516
+ detail: sliceClass.guards.length ? [`guards: ${sliceClass.guards.join(", ")}`] : []
517
+ });
518
+ if (modelDoc)
519
+ this.#add({
520
+ ...base,
521
+ family: "module",
522
+ sourceKey: modelDoc.name,
523
+ span: modelDoc.span,
524
+ name: refName,
525
+ role: "db",
526
+ gate: "db",
527
+ label: modelDoc.name,
528
+ detail: []
529
+ });
530
+ const modelRoles = modelNames(refName);
531
+ const modelNameSet = new Set(Object.values(modelRoles));
532
+ if (modelClass) {
533
+ const target = { ...base, family: "model", sourceKey: modelClass.name, span: modelClass.span };
534
+ for (const [role, name] of Object.entries(modelRoles))
535
+ this.#add({
536
+ ...target,
537
+ name,
538
+ role,
539
+ gate: role === "model" ? "state" : null,
540
+ label: `model ${Model}`,
541
+ detail: []
542
+ });
543
+ for (const [role, name] of Object.entries(databaseStateNames(refName)))
544
+ this.#add({
545
+ ...target,
546
+ name: stateSetterName(name),
547
+ role: `set:${role}`,
548
+ gate: null,
549
+ label: `model ${Model}`,
550
+ detail: []
551
+ });
552
+ for (const field of NameTable.#fieldsOf(modelClass, constants)) {
553
+ const setters = formSetterNames(Model, field.name);
554
+ for (const role of [
555
+ "setFieldOnModel",
556
+ "addFieldOnModel",
557
+ "subFieldOnModel",
558
+ "addOrSubFieldOnModel",
559
+ "uploadFieldOnModel"
560
+ ])
561
+ this.#add({
562
+ ...base,
563
+ family: "formSetter",
564
+ sourceKey: field.name,
565
+ span: field.span,
566
+ name: setters[role],
567
+ role,
568
+ gate: null,
569
+ label: `field ${field.declaredIn}.${field.name}`,
570
+ detail: []
571
+ });
572
+ }
573
+ }
574
+ for (const slice of group.slices) {
575
+ const defaultTarget = {
576
+ ...base,
577
+ family: "slice",
578
+ sourceKey: slice.name,
579
+ span: slice.span,
580
+ label: `slice ${refName} (default)`,
581
+ detail: slice.guards.length ? [`guards: ${slice.guards.join(", ")}`] : []
582
+ };
583
+ this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
584
+ for (const key of slice.keys)
585
+ this.#addSliceNames({
586
+ ...base,
587
+ family: "slice",
588
+ sourceKey: key.name,
589
+ span: key.span,
590
+ label: `slice ${key.name}`,
591
+ detail: [...key.guards.length ? [`guards: ${key.guards.join(", ")}`] : [], ...key.live ? ["live"] : []]
592
+ }, refName, key.name, modelNameSet, slice.name);
593
+ }
594
+ if (filterClass) {
595
+ const defaults = { ...base, family: "query", detail: [] };
596
+ for (const [role, name] of Object.entries(queryNames("any")))
597
+ this.#add({
598
+ ...defaults,
599
+ sourceKey: filterClass.name,
600
+ span: filterClass.span,
601
+ name,
602
+ role,
603
+ gate: null,
604
+ label: `query any (default) of ${filterClass.name}`
605
+ });
606
+ for (const query of filterClass.queries)
607
+ for (const [role, name] of Object.entries(queryNames(query.name)))
608
+ this.#add({
609
+ ...defaults,
610
+ sourceKey: query.name,
611
+ span: query.span,
612
+ name,
613
+ role,
614
+ gate: null,
615
+ label: `query ${query.name}`,
616
+ renamed: (key) => queryNames(key)[role],
617
+ dictionary: { section: "query", className: filterClass.name }
618
+ });
619
+ }
620
+ for (const store of facts.flatMap((file) => file.stores).filter((item) => item.model === refName))
621
+ for (const key of store.stateKeys)
622
+ this.#add({
623
+ ...base,
624
+ family: "storeState",
625
+ sourceKey: key.name,
626
+ span: key.span,
627
+ name: stateSetterName(key.name),
628
+ role: "set",
629
+ gate: null,
630
+ label: `store state ${key.name}`,
631
+ detail: []
632
+ });
633
+ for (const endpoint of facts.flatMap((file) => file.endpoints).filter((item) => item.model === refName))
634
+ for (const key of endpoint.keys) {
635
+ const derived = endpointNames(key.name);
636
+ const name = key.verb === "pubsub" ? derived.subscribe : key.verb === "message" ? derived.listen : null;
637
+ if (!name)
638
+ continue;
639
+ this.#add({
640
+ ...base,
641
+ family: "endpoint",
642
+ sourceKey: key.name,
643
+ span: key.span,
644
+ name,
645
+ role: key.verb === "pubsub" ? "subscribe" : "listen",
646
+ gate: null,
647
+ label: `${key.verb} ${key.name}`,
648
+ detail: key.guards.length ? [`guards: ${key.guards.join(", ")}`] : []
649
+ });
650
+ }
651
+ }
652
+ static #sliceRoles(refName, suffix) {
653
+ const names = sliceNames(refName, suffix);
654
+ return new Map([
655
+ ["slice", names.slice],
656
+ ...Object.entries(names.state),
657
+ ...Object.entries(names.action),
658
+ ...Object.entries(names.endpoint),
659
+ ...Object.entries(names.state).map(([role, name]) => [`set:${role}`, stateSetterName(name)])
660
+ ]);
661
+ }
662
+ #addSliceNames(target, refName, suffix, skip, className) {
663
+ for (const [role, name] of NameTable.#sliceRoles(refName, suffix)) {
664
+ if (!suffix && role !== "slice" && skip.has(name))
665
+ continue;
666
+ this.#add({
667
+ ...target,
668
+ name,
669
+ role,
670
+ gate: role === "slice" ? "slice" : null,
671
+ ...suffix && className ? {
672
+ renamed: (key) => NameTable.#sliceRoles(refName, key).get(role) ?? name,
673
+ dictionary: { section: "slice", className }
674
+ } : {}
675
+ });
676
+ }
677
+ }
678
+ static #fieldsOf(modelClass, { own, libs }) {
679
+ const classNamed = (name, near) => own.find((constant) => constant.name === name && constant.span.fileName === near.span.fileName) ?? own.find((constant) => constant.name === name) ?? libs.find((constant) => constant.name === name);
680
+ const fields = new Map;
681
+ const seen = new Set;
682
+ const visit = (constant) => {
683
+ if (seen.has(constant))
684
+ return;
685
+ seen.add(constant);
686
+ for (const field of constant.fields)
687
+ if (!fields.has(field.name))
688
+ fields.set(field.name, { ...field, declaredIn: constant.name });
689
+ for (const parent of constant.parents) {
690
+ const parentClass = classNamed(parent, constant);
691
+ if (parentClass)
692
+ visit(parentClass);
693
+ }
694
+ };
695
+ visit(modelClass);
696
+ return [...fields.values()];
697
+ }
698
+ }
699
+
700
+ // pkgs/@akanjs/language/akanIndex.ts
701
+ var sourcePattern = /\.(constant|document|service|signal|store)\.ts$/;
702
+ var dictionaryPattern = /\.dictionary\.ts$/;
703
+ var wordPattern = /[A-Za-z_$][\w$]*/g;
704
+ var identifierPattern = /^[A-Za-z_$][\w$]*$/;
705
+ var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
706
+ var stateReceivers = new Set(["use", "sel", "get", "do"]);
707
+ var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
708
+ var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
709
+ var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
710
+ var parseCacheSize = 64;
711
+ var pathOf = (fileName) => fileName.replace(/\\/g, "/");
712
+ var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
713
+ var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
714
+
715
+ class AkanIndex {
716
+ #ts;
717
+ #root;
718
+ #ast;
719
+ #reader;
720
+ #facts = new Map;
721
+ #fileWords = new Map;
722
+ #parsed = new Map;
723
+ #table = null;
724
+ constructor({ ts, root }) {
725
+ this.#ts = ts;
726
+ this.#root = pathOf(root).replace(/\/$/, "");
727
+ this.#ast = createAst(ts);
728
+ this.#reader = createFactReader(ts, this.#ast);
729
+ }
730
+ static isSourcePath(fileName) {
731
+ return sourcePattern.test(fileName);
732
+ }
733
+ get fileCount() {
734
+ return this.#fileWords.size;
735
+ }
736
+ setFile(givenName, text) {
737
+ const fileName = pathOf(givenName);
738
+ this.#fileWords.set(fileName, `
739
+ ${[...new Set(text.match(wordPattern))].join(`
740
+ `)}
741
+ `);
742
+ if (!AkanIndex.isSourcePath(fileName))
743
+ return;
744
+ const source = this.#parsed.get(fileName)?.text === text ? this.#parse(fileName, text) : this.#parseUncached(fileName, text);
745
+ this.#facts.set(fileName, this.#reader.read(source, this.#ownerOf(fileName)));
746
+ this.#table = null;
747
+ }
748
+ removeFile(givenName) {
749
+ const fileName = pathOf(givenName);
750
+ this.#fileWords.delete(fileName);
751
+ this.#parsed.delete(fileName);
752
+ if (this.#facts.delete(fileName))
753
+ this.#table = null;
754
+ }
755
+ definitionAt(givenName, text, offset) {
756
+ const fileName = pathOf(givenName);
757
+ const site = this.#useAt(this.#parse(fileName, text), offset);
758
+ return site ? this.#resolve(fileName, site) : [];
759
+ }
760
+ hoverAt(givenName, text, offset) {
761
+ const fileName = pathOf(givenName);
762
+ const site = this.#useAt(this.#parse(fileName, text), offset);
763
+ if (!site)
764
+ return null;
765
+ const targets = this.#resolve(fileName, site);
766
+ return targets.length ? { name: site.identifier.text, targets } : null;
767
+ }
768
+ sourceKeysOf(givenName) {
769
+ const keys = new Map;
770
+ for (const entry of this.#nameTable().entriesTargeting(pathOf(givenName))) {
771
+ if (!lensFamilies.has(entry.family) || keys.has(entry.span.start))
772
+ continue;
773
+ keys.set(entry.span.start, AkanIndex.#sourceKeyOf(entry));
774
+ }
775
+ return [...keys.values()].sort((a, b) => a.span.start - b.span.start);
776
+ }
777
+ sourceKeyAt(givenName, offset) {
778
+ const entries = this.#sourceEntriesAt(pathOf(givenName), offset);
779
+ const entry = entries.find((candidate) => lensFamilies.has(candidate.family)) ?? entries.at(0);
780
+ return entry ? AkanIndex.#sourceKeyOf(entry) : null;
781
+ }
782
+ referencesAt(givenName, offset, readText) {
783
+ return this.#usesOf(this.#sourceEntriesAt(pathOf(givenName), offset), readText).map((use) => use.span);
784
+ }
785
+ renameTargetAt(givenName, offset) {
786
+ const source = this.#sourceEntriesAt(pathOf(givenName), offset).find((entry) => entry.renamed);
787
+ return source ? { family: source.family, sourceKey: source.sourceKey, span: source.span } : null;
788
+ }
789
+ renameAt(givenName, offset, newKey, readText) {
790
+ const sources = this.#sourceEntriesAt(pathOf(givenName), offset).filter((entry) => entry.renamed);
791
+ if (!sources.length || !identifierPattern.test(newKey))
792
+ return null;
793
+ return [
794
+ { span: sources[0].span, newText: newKey },
795
+ ...this.#usesOf(sources, readText).map(({ span, entry, shorthand }) => {
796
+ const renamed = entry.renamed?.(newKey) ?? entry.name;
797
+ return { span, newText: shorthand ? `${renamed}: ${entry.name}` : renamed };
798
+ }),
799
+ ...this.#dictionaryKeysOf(sources[0], readText).map(({ span, quote }) => ({
800
+ span,
801
+ newText: `${quote}${newKey}${quote}`
802
+ }))
803
+ ];
804
+ }
805
+ static #sourceKeyOf({ family, sourceKey, label, detail, span }) {
806
+ return { family, sourceKey, label, detail, span };
807
+ }
808
+ #usesOf(sources, readText) {
809
+ if (!sources.length)
810
+ return [];
811
+ const ts = this.#ts;
812
+ const isSource = (target) => sources.some((source) => source.span.fileName === target.span.fileName && source.span.start === target.span.start);
813
+ const refNames = new Set(sources.map((source) => source.refName));
814
+ const byName = new Map(sources.map((source) => [source.name, source]));
815
+ const naming = new RegExp(`\\n(?:${[...byName.keys()].map(escapeRegExp).join("|")})\\n`);
816
+ const gateWordsOf = sources.map((source) => source.gate ? gateWords[source.gate] : undefined);
817
+ const receiverPatterns = sources.map((source, at) => {
818
+ const gateWord = gateWordsOf[at];
819
+ return gateWord ? new RegExp(String.raw`(?<![\w$])${gateWord}${receiverGap}[?!]?\.${receiverGap}${escapeRegExp(source.name)}(?![\w$])`) : null;
820
+ });
821
+ const named = (words, at) => words.includes(`
822
+ ${sources[at].name}
823
+ `) && (!gateWordsOf[at] || words.includes(`
824
+ ${gateWordsOf[at]}
825
+ `));
826
+ const candidates = [...this.#fileWords].filter(([, words]) => naming.test(words) && sources.some((_, at) => named(words, at)));
827
+ const found = [];
828
+ for (const [file, words] of candidates.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
829
+ const text = readText(file);
830
+ if (text === undefined)
831
+ continue;
832
+ if (!sources.some((_, at) => named(words, at) && (receiverPatterns[at]?.test(text) ?? true)))
833
+ continue;
834
+ const source = this.#parse(file, text);
835
+ const visit = (node) => {
836
+ const entry = ts.isIdentifier(node) ? byName.get(node.text) : undefined;
837
+ const site = entry && ts.isIdentifier(node) ? this.#siteOf(node) : null;
838
+ const targets = site ? this.#resolve(file, site) : [];
839
+ if (entry && site && targets.some(isSource) && targets.every((target) => refNames.has(target.refName))) {
840
+ const parent = site.identifier.parent;
841
+ const shorthand = ts.isBindingElement(parent) && !parent.propertyName;
842
+ found.push({ span: this.#reader.spanOf(source, site.identifier), entry, shorthand });
843
+ }
844
+ ts.forEachChild(node, visit);
845
+ };
846
+ visit(source);
847
+ }
848
+ return found;
849
+ }
850
+ #dictionaryKeysOf(source, readText) {
851
+ const { dictionary } = source;
852
+ if (!dictionary)
853
+ return [];
854
+ const ts = this.#ts;
855
+ const keys = [];
856
+ const moduleDir = source.span.fileName.slice(0, source.span.fileName.lastIndexOf("/"));
857
+ for (const [file, words] of this.#fileWords) {
858
+ if (!dictionaryPattern.test(file) || this.#ownerOf(file) !== source.owner)
859
+ continue;
860
+ const sameModule = file.slice(0, file.lastIndexOf("/")) === moduleDir;
861
+ if (!words.includes(`
862
+ ${dictionary.section}
863
+ `))
864
+ continue;
865
+ if (!sameModule && !words.includes(`
866
+ ${dictionary.className}
867
+ `))
868
+ continue;
869
+ const text = readText(file);
870
+ if (text === undefined)
871
+ continue;
872
+ const parsed = this.#parse(file, text);
873
+ const visit = (node) => {
874
+ const typeArgument = ts.isCallExpression(node) ? node.typeArguments?.[0] : undefined;
875
+ const builder = ts.isCallExpression(node) ? node.arguments.at(-1) : undefined;
876
+ const typed = !!typeArgument && ts.isTypeReferenceNode(typeArgument) && ts.isIdentifier(typeArgument.typeName) && typeArgument.typeName.text === dictionary.className;
877
+ const untyped = !typeArgument && sameModule;
878
+ const object = ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === dictionary.section && (typed || untyped) && builder ? this.#ast.firstObjectReturnedByArrow(builder) : null;
879
+ for (const property of object?.properties ?? [])
880
+ if (ts.isPropertyAssignment(property) && this.#ast.nodeName(property.name) === source.sourceKey)
881
+ keys.push({
882
+ span: this.#reader.spanOf(parsed, property.name),
883
+ quote: ts.isStringLiteral(property.name) ? parsed.text.charAt(property.name.getStart(parsed)) : ""
884
+ });
885
+ ts.forEachChild(node, visit);
886
+ };
887
+ visit(parsed);
888
+ }
889
+ return keys;
890
+ }
891
+ #sourceEntriesAt(fileName, offset) {
892
+ return this.#nameTable().entriesTargeting(fileName).filter((entry) => entry.span.start <= offset && offset <= entry.span.end);
893
+ }
894
+ #nameTable() {
895
+ this.#table ??= new NameTable(this.#facts.values());
896
+ return this.#table;
897
+ }
898
+ #relative(fileName) {
899
+ return fileName.startsWith(`${this.#root}/`) ? fileName.slice(this.#root.length + 1) : fileName;
900
+ }
901
+ #ownerOf(fileName) {
902
+ return ownerOf(this.#relative(fileName));
903
+ }
904
+ #parseUncached(fileName, text) {
905
+ const kind = fileName.endsWith(".tsx") ? this.#ts.ScriptKind.TSX : this.#ts.ScriptKind.TS;
906
+ return this.#ast.sourceFileFor(fileName, text, kind);
907
+ }
908
+ #parse(fileName, text) {
909
+ const cached = this.#parsed.get(fileName);
910
+ if (cached?.text === text)
911
+ return cached.source;
912
+ const source = this.#parseUncached(fileName, text);
913
+ this.#parsed.delete(fileName);
914
+ this.#parsed.set(fileName, { text, source });
915
+ if (this.#parsed.size > parseCacheSize) {
916
+ const oldest = this.#parsed.keys().next().value;
917
+ if (oldest !== undefined)
918
+ this.#parsed.delete(oldest);
919
+ }
920
+ return source;
921
+ }
922
+ #useAt(source, offset) {
923
+ const ts = this.#ts;
924
+ let found = null;
925
+ const visit = (node) => {
926
+ if (offset < node.getStart(source) || offset > node.getEnd())
927
+ return;
928
+ if (ts.isIdentifier(node))
929
+ found = node;
930
+ ts.forEachChild(node, visit);
931
+ };
932
+ visit(source);
933
+ return found ? this.#siteOf(found) : null;
934
+ }
935
+ #siteOf(identifier) {
936
+ const ts = this.#ts;
937
+ const parent = identifier.parent;
938
+ let chain;
939
+ if (ts.isPropertyAccessExpression(parent) && parent.name === identifier)
940
+ chain = this.#chainOf(parent.expression);
941
+ else if (ts.isBindingElement(parent) && !parent.dotDotDotToken && ts.isObjectBindingPattern(parent.parent) && (parent.propertyName === identifier || !parent.propertyName && parent.name === identifier))
942
+ chain = null;
943
+ else
944
+ return null;
945
+ const { model, inStore } = this.#classContextOf(identifier);
946
+ return { identifier, chain, classModel: model, inStore };
947
+ }
948
+ #chainOf(expression) {
949
+ const ts = this.#ts;
950
+ if (ts.isNonNullExpression(expression) || ts.isParenthesizedExpression(expression))
951
+ return this.#chainOf(expression.expression);
952
+ if (ts.isIdentifier(expression))
953
+ return [expression.text];
954
+ if (expression.kind === ts.SyntaxKind.ThisKeyword)
955
+ return ["this"];
956
+ if (ts.isPropertyAccessExpression(expression))
957
+ return [...this.#chainOf(expression.expression), expression.name.text];
958
+ return ["?"];
959
+ }
960
+ #classContextOf(node) {
961
+ const ts = this.#ts;
962
+ let current = node.parent;
963
+ while (current && !ts.isClassDeclaration(current) && !ts.isClassExpression(current))
964
+ current = current.parent;
965
+ if (!current)
966
+ return { model: null, inStore: false };
967
+ const className = current.name?.text;
968
+ const heritage = current.heritageClauses?.flatMap((clause) => [...clause.types]) ?? [];
969
+ for (const { expression } of heritage) {
970
+ if (!ts.isCallExpression(expression))
971
+ continue;
972
+ const callee = this.#ast.expressionName(expression.expression);
973
+ if (callee === "into" && className?.endsWith("Model"))
974
+ return { model: className.charAt(0).toLowerCase() + className.slice(1, -5), inStore: false };
975
+ const holder = callee === "serve" ? "db" : callee === "store" ? "sig" : callee === "slice" || callee === "endpoint" || callee === "internal" ? "srv" : null;
976
+ if (!holder)
977
+ continue;
978
+ return { model: this.#reader.moduleRefOf(expression.arguments[0], holder), inStore: callee === "store" };
979
+ }
980
+ return { model: null, inStore: false };
981
+ }
982
+ #modelHintOf(site) {
983
+ const { chain } = site;
984
+ if (!chain)
985
+ return null;
986
+ if (chain.length === 1 && chain[0] === "this")
987
+ return site.classModel;
988
+ const holder = chain.at(-1)?.replace(/^#/, "");
989
+ const suffix = holder?.match(/.(Service|Model)$/)?.[1];
990
+ if (holder && suffix)
991
+ return holder.slice(0, -suffix.length);
992
+ if (chain.length === 2 && (chain[0] === "db" || chain[0] === "srv"))
993
+ return chain[1];
994
+ return null;
995
+ }
996
+ #passesGate(entry, site) {
997
+ const { chain } = site;
998
+ const last = chain?.at(-1);
999
+ switch (entry.gate) {
1000
+ case "srv":
1001
+ case "sig":
1002
+ case "db":
1003
+ return chain?.length === 1 && last === entry.gate;
1004
+ case "slice":
1005
+ return last === "slice";
1006
+ case "state":
1007
+ return !!last && stateReceivers.has(last) || site.inStore && chain?.length === 1 && last === "this";
1008
+ default:
1009
+ return true;
1010
+ }
1011
+ }
1012
+ #resolve(fileName, site) {
1013
+ const owner = this.#ownerOf(fileName);
1014
+ const visible = this.#nameTable().lookup(site.identifier.text).filter((entry) => this.#passesGate(entry, site) && (!owner || !entry.owner || entry.owner === owner || entry.owner.startsWith("libs/")));
1015
+ const hint = this.#modelHintOf(site);
1016
+ const hinted = hint ? visible.filter((entry) => entry.refName === hint) : [];
1017
+ const chosen = hinted.length ? hinted : hint ? visible.filter((entry) => !modellessFamilies.has(entry.family)) : visible;
1018
+ return chosen.map(({ family, refName, owner: entryOwner, sourceKey, label, detail, span }) => ({
1019
+ family,
1020
+ refName,
1021
+ owner: entryOwner,
1022
+ sourceKey,
1023
+ label,
1024
+ detail,
1025
+ span
1026
+ })).sort((a, b) => Number(b.owner === owner) - Number(a.owner === owner));
1027
+ }
1028
+ }
1029
+ export {
1030
+ AkanIndex,
1031
+ createAst,
1032
+ createFactReader,
1033
+ ownerOf
1034
+ };