agentlang 0.6.6 → 0.6.7

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 (43) hide show
  1. package/out/cli/main.d.ts +1 -1
  2. package/out/cli/main.d.ts.map +1 -1
  3. package/out/cli/main.js +54 -3
  4. package/out/cli/main.js.map +1 -1
  5. package/out/runtime/api.d.ts +4 -5
  6. package/out/runtime/api.d.ts.map +1 -1
  7. package/out/runtime/api.js +10 -3
  8. package/out/runtime/api.js.map +1 -1
  9. package/out/runtime/defs.d.ts +12 -0
  10. package/out/runtime/defs.d.ts.map +1 -1
  11. package/out/runtime/defs.js +46 -0
  12. package/out/runtime/defs.js.map +1 -1
  13. package/out/runtime/interpreter.d.ts.map +1 -1
  14. package/out/runtime/interpreter.js +16 -2
  15. package/out/runtime/interpreter.js.map +1 -1
  16. package/out/runtime/loader.d.ts +1 -0
  17. package/out/runtime/loader.d.ts.map +1 -1
  18. package/out/runtime/loader.js +5 -0
  19. package/out/runtime/loader.js.map +1 -1
  20. package/out/runtime/module.d.ts +1 -1
  21. package/out/runtime/module.d.ts.map +1 -1
  22. package/out/runtime/module.js +12 -10
  23. package/out/runtime/module.js.map +1 -1
  24. package/out/runtime/modules/core.d.ts +4 -0
  25. package/out/runtime/modules/core.d.ts.map +1 -1
  26. package/out/runtime/modules/core.js +72 -1
  27. package/out/runtime/modules/core.js.map +1 -1
  28. package/out/runtime/resolvers/interface.d.ts.map +1 -1
  29. package/out/runtime/resolvers/interface.js +7 -0
  30. package/out/runtime/resolvers/interface.js.map +1 -1
  31. package/out/runtime/resolvers/sqldb/database.d.ts.map +1 -1
  32. package/out/runtime/resolvers/sqldb/database.js +61 -4
  33. package/out/runtime/resolvers/sqldb/database.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/cli/main.ts +70 -3
  36. package/src/runtime/api.ts +15 -3
  37. package/src/runtime/defs.ts +59 -0
  38. package/src/runtime/interpreter.ts +17 -2
  39. package/src/runtime/loader.ts +7 -0
  40. package/src/runtime/module.ts +14 -11
  41. package/src/runtime/modules/core.ts +96 -1
  42. package/src/runtime/resolvers/interface.ts +7 -0
  43. package/src/runtime/resolvers/sqldb/database.ts +64 -5
@@ -4,6 +4,18 @@ import {
4
4
  isInstanceOfType as al_isInstanceOfType,
5
5
  } from './module.js';
6
6
 
7
- export const makeInstance = al_makeInstance;
8
- export const isInstanceOfType = al_isInstanceOfType;
9
- export const fetchConfig = al_fetchConfig;
7
+ declare global {
8
+ var agentlang: any | undefined;
9
+ }
10
+
11
+ let ApiInited = false;
12
+
13
+ export function initGlobalApi() {
14
+ if (!ApiInited) {
15
+ globalThis.agentlang = {};
16
+ globalThis.agentlang.makeInstance = al_makeInstance;
17
+ globalThis.agentlang.isInstanceOfType = al_isInstanceOfType;
18
+ globalThis.agentlang.fetchConfig = al_fetchConfig;
19
+ ApiInited = true;
20
+ }
21
+ }
@@ -327,3 +327,62 @@ export type FkSpec = {
327
327
  onDelete: 'CASCADE' | 'SET NULL' | 'SET DEFAULT' | undefined;
328
328
  onUpdate: 'CASCADE' | 'SET NULL' | 'SET DEFAULT' | undefined;
329
329
  };
330
+
331
+ enum RuntimeModeTag {
332
+ DEV,
333
+ PROD,
334
+ INIT_SCHEMA,
335
+ RUN_MIGRATION,
336
+ UNDO_MIGRATION,
337
+ GENERATE_MIGRATION,
338
+ }
339
+
340
+ let RuntimeMode: RuntimeModeTag = RuntimeModeTag.DEV;
341
+
342
+ export function setRuntimeMode_dev() {
343
+ RuntimeMode = RuntimeModeTag.DEV;
344
+ }
345
+
346
+ export function setRuntimeMode_prod() {
347
+ RuntimeMode = RuntimeModeTag.PROD;
348
+ }
349
+
350
+ export function setRuntimeMode_init_schema() {
351
+ RuntimeMode = RuntimeModeTag.INIT_SCHEMA;
352
+ }
353
+
354
+ export function setRuntimeMode_migration() {
355
+ RuntimeMode = RuntimeModeTag.RUN_MIGRATION;
356
+ }
357
+
358
+ export function setRuntimeMode_undo_migration() {
359
+ RuntimeMode = RuntimeModeTag.UNDO_MIGRATION;
360
+ }
361
+
362
+ export function setRuntimeMode_generate_migration() {
363
+ RuntimeMode = RuntimeModeTag.GENERATE_MIGRATION;
364
+ }
365
+
366
+ export function isRuntimeMode_dev(): boolean {
367
+ return RuntimeMode === RuntimeModeTag.DEV;
368
+ }
369
+
370
+ export function isRuntimeMode_prod(): boolean {
371
+ return RuntimeMode === RuntimeModeTag.PROD;
372
+ }
373
+
374
+ export function isRuntimeMode_init_schema(): boolean {
375
+ return RuntimeMode === RuntimeModeTag.INIT_SCHEMA;
376
+ }
377
+
378
+ export function isRuntimeMode_migration(): boolean {
379
+ return RuntimeMode === RuntimeModeTag.RUN_MIGRATION;
380
+ }
381
+
382
+ export function isRuntimeMode_generate_migration(): boolean {
383
+ return RuntimeMode === RuntimeModeTag.GENERATE_MIGRATION;
384
+ }
385
+
386
+ export function isRuntimeMode_undo_migration(): boolean {
387
+ return RuntimeMode === RuntimeModeTag.UNDO_MIGRATION;
388
+ }
@@ -1579,6 +1579,7 @@ async function evaluateCrudMap(crud: CrudMap, env: Environment): Promise<void> {
1579
1579
  const res: Array<Instance> = new Array<Instance>();
1580
1580
  for (let i = 0; i < lastRes.length; ++i) {
1581
1581
  await computeExprAttributes(lastRes[i], crud.body?.attributes, attrs, env);
1582
+ env.attributes.set('__patch', attrs);
1582
1583
  await runPreUpdateEvents(lastRes[i], env);
1583
1584
  maybeSetMetaAttributes(attrs, env, true);
1584
1585
  const finalInst: Instance = await resolver.updateInstance(lastRes[i], attrs);
@@ -2372,9 +2373,23 @@ export async function callPostEventOnSubscription(
2372
2373
  inst: Instance,
2373
2374
  env?: Environment
2374
2375
  ): Promise<any> {
2376
+ const localEnv = env === undefined;
2375
2377
  const newEnv = env ? env : new Environment('onSubs.env');
2376
- await runPrePostEvents(crudType, false, inst, newEnv);
2377
- return newEnv.getLastResult();
2378
+ try {
2379
+ await runPrePostEvents(crudType, false, inst, newEnv);
2380
+ if (localEnv) {
2381
+ await newEnv.commitAllTransactions();
2382
+ }
2383
+ return newEnv.getLastResult();
2384
+ } catch (reason: any) {
2385
+ if (localEnv) {
2386
+ await newEnv.rollbackAllTransactions();
2387
+ logger.error(
2388
+ `callPostEventOnSubscription failed for ${crudType} ${inst.getFqName()} - ${reason}`
2389
+ );
2390
+ }
2391
+ }
2392
+ return undefined;
2378
2393
  }
2379
2394
 
2380
2395
  async function runPrePostEvents(
@@ -191,6 +191,12 @@ export const DefaultAppSpec: ApplicationSpec = {
191
191
  version: '0.0.1',
192
192
  };
193
193
 
194
+ let CurrentAppSpec: ApplicationSpec = DefaultAppSpec;
195
+
196
+ export function getAppSpec(): ApplicationSpec {
197
+ return CurrentAppSpec;
198
+ }
199
+
194
200
  async function getAllModules(
195
201
  dir: string,
196
202
  fs: ExtendedFileSystem,
@@ -234,6 +240,7 @@ async function loadApp(appDir: string, fsOptions?: any, callback?: Function): Pr
234
240
  const appJsonFile = `${appDir}${path.sep}package.json`;
235
241
  const s: string = await fs.readFile(appJsonFile);
236
242
  const appSpec: ApplicationSpec = JSON.parse(s);
243
+ CurrentAppSpec = appSpec;
237
244
  if (dependenciesCallback !== undefined && appSpec.dependencies) {
238
245
  const aldeps = new Array<DependencyInfo>();
239
246
  for (const [k, v] of Object.entries(appSpec.dependencies)) {
@@ -399,6 +399,15 @@ export class Record extends ModuleEntry {
399
399
  this.compositeUqAttributes = findUqCompositeAttributes(scm);
400
400
  }
401
401
 
402
+ protected addMetaAttributes(): Record {
403
+ this.schema
404
+ .set(SysAttr_Created, SysAttr_CreatedSpec)
405
+ .set(SysAttr_CreatedBy, SysAttr_CreatedBySpec)
406
+ .set(SysAttr_LastModified, SysAttr_LastModifiedSpec)
407
+ .set(SysAttr_LastModifiedBy, SysAttr_LastModifiedBySpec);
408
+ return this;
409
+ }
410
+
402
411
  private addOneOfRefAttribute(s: string): Record {
403
412
  if (this.oneOfRefAttributes === undefined) {
404
413
  this.oneOfRefAttributes = [];
@@ -1152,15 +1161,6 @@ export class Entity extends Record {
1152
1161
  this.addMetaAttributes();
1153
1162
  }
1154
1163
 
1155
- private addMetaAttributes(): Entity {
1156
- this.schema
1157
- .set(SysAttr_Created, SysAttr_CreatedSpec)
1158
- .set(SysAttr_CreatedBy, SysAttr_CreatedBySpec)
1159
- .set(SysAttr_LastModified, SysAttr_LastModifiedSpec)
1160
- .set(SysAttr_LastModifiedBy, SysAttr_LastModifiedBySpec);
1161
- return this;
1162
- }
1163
-
1164
1164
  setRbacSpecifications(rbac: RbacSpecification[]): Entity {
1165
1165
  this.rbac = rbac;
1166
1166
  return this;
@@ -1254,7 +1254,10 @@ export class Relationship extends Record {
1254
1254
  props?: Map<string, any>
1255
1255
  ) {
1256
1256
  super(name, moduleName, scm);
1257
- if (typ == 'between') this.relType = RelType.BETWEEN;
1257
+ if (typ == 'between') {
1258
+ this.relType = RelType.BETWEEN;
1259
+ this.addMetaAttributes();
1260
+ }
1258
1261
  this.node1 = node1;
1259
1262
  this.node2 = node2;
1260
1263
  this.properties = props;
@@ -3261,7 +3264,7 @@ function checkOneOfValue(attrSpec: AttributeSpec, attrName: string, attrValue: a
3261
3264
  const vals: Set<string> | undefined = getEnumValues(attrSpec);
3262
3265
  if (vals) {
3263
3266
  if (!vals.has(attrValue as string)) {
3264
- throw new Error(`Value of ${attrName} must be one of ${vals}`);
3267
+ throw new Error(`Value of ${attrName} must be one of [${[...vals]}]`);
3265
3268
  }
3266
3269
  return true;
3267
3270
  }
@@ -1,7 +1,13 @@
1
1
  import { default as ai } from './ai.js';
2
2
  import { default as auth } from './auth.js';
3
3
  import { default as files } from './files.js';
4
- import { DefaultModuleName, DefaultModules, escapeSpecialChars, isString } from '../util.js';
4
+ import {
5
+ DefaultModuleName,
6
+ DefaultModules,
7
+ escapeSpecialChars,
8
+ isString,
9
+ restoreSpecialChars,
10
+ } from '../util.js';
5
11
  import { Instance, isInstanceOfType, makeInstance, newInstanceAttributes } from '../module.js';
6
12
  import {
7
13
  Environment,
@@ -120,6 +126,12 @@ event validateModule extends ValidationRequest {
120
126
  workflow validateModule {
121
127
  await Core.validateModule(validateModule.data)
122
128
  }
129
+
130
+ entity Migration {
131
+ appVersion String @id,
132
+ ups String @optional,
133
+ downs String @optional
134
+ }
123
135
  `;
124
136
 
125
137
  export const CoreModules: string[] = [];
@@ -404,3 +416,86 @@ export async function validateModule(moduleDef: any): Promise<Instance> {
404
416
  );
405
417
  }
406
418
  }
419
+
420
+ const SqlSep = ';\n\n';
421
+
422
+ export async function saveMigration(
423
+ version: string,
424
+ ups: string[] | undefined,
425
+ downs: string[] | undefined
426
+ ): Promise<boolean> {
427
+ try {
428
+ const env = new Environment(`migrations-${version}-env`);
429
+ await parseAndEvaluateStatement(
430
+ `purge {agentlang/Migration {appVersion? "${version}"}}`,
431
+ undefined,
432
+ env
433
+ );
434
+ let ups_str = '';
435
+ if (ups) {
436
+ ups_str = escapeSpecialChars(
437
+ ups
438
+ .map((s: string) => {
439
+ return s.trim();
440
+ })
441
+ .join(SqlSep)
442
+ );
443
+ }
444
+ let downs_str = '';
445
+ if (downs) {
446
+ downs_str = escapeSpecialChars(
447
+ downs
448
+ .map((s: string) => {
449
+ return s.trim();
450
+ })
451
+ .join(SqlSep)
452
+ );
453
+ }
454
+ const inst: Instance = await parseAndEvaluateStatement(`{agentlang/Migration {
455
+ appVersion "${version}",
456
+ ups "${ups_str}",
457
+ downs "${downs_str}"}}`);
458
+ if (isInstanceOfType(inst, 'agentlang/Migration') && inst.lookup('appVersion') === version) {
459
+ await env.commitAllTransactions();
460
+ return true;
461
+ } else {
462
+ logger.warn(`Failed to save migration for version ${version}`);
463
+ }
464
+ } catch (reason: any) {
465
+ logger.error(`Failed to save migration for version ${version} - ${reason}`);
466
+ }
467
+ return false;
468
+ }
469
+
470
+ export async function loadMigration(version: string): Promise<Instance | undefined> {
471
+ try {
472
+ const env = new Environment(`migrations-${version}-env`);
473
+ const insts: Instance[] = await parseAndEvaluateStatement(
474
+ `{agentlang/Migration {appVersion? "${version}"}}`,
475
+ undefined,
476
+ env
477
+ );
478
+ if (insts && insts.length > 0) {
479
+ return insts[0];
480
+ }
481
+ } catch (reason: any) {
482
+ logger.error(`Failed to lookup migration for version ${version} - ${reason}`);
483
+ }
484
+ return undefined;
485
+ }
486
+
487
+ export function migrationUps(inst: Instance): string[] | undefined {
488
+ const ups: string | undefined = inst.lookup('ups');
489
+ if (ups) {
490
+ return restoreSpecialChars(ups).split(SqlSep);
491
+ }
492
+ return undefined;
493
+ }
494
+
495
+ export function migrationDowns(inst: Instance): string[] | undefined {
496
+ const downs: string | undefined = inst.lookup('downs');
497
+ if (downs) {
498
+ return restoreSpecialChars(downs).split(SqlSep);
499
+ }
500
+ return undefined;
501
+ }
@@ -329,6 +329,8 @@ export class GenericResolver extends Resolver {
329
329
  }
330
330
 
331
331
  override async subscribe() {
332
+ const MaxErrors = 3;
333
+ let errCount = 0;
332
334
  while (true) {
333
335
  try {
334
336
  if (this.subs?.subscribe) {
@@ -338,6 +340,11 @@ export class GenericResolver extends Resolver {
338
340
  return;
339
341
  } catch (reason: any) {
340
342
  logger.warn(`subscribe error in resolver ${this.name}: ${reason}`);
343
+ if (errCount >= MaxErrors) {
344
+ logger.warn(`exiting resolver subscription after ${errCount} retries`);
345
+ break;
346
+ }
347
+ ++errCount;
341
348
  }
342
349
  }
343
350
  }
@@ -29,9 +29,16 @@ import { isString } from '../../util.js';
29
29
  import {
30
30
  DeletedFlagAttributeName,
31
31
  ForceReadPermFlag,
32
+ isRuntimeMode_dev,
33
+ isRuntimeMode_generate_migration,
34
+ isRuntimeMode_init_schema,
35
+ isRuntimeMode_migration,
36
+ isRuntimeMode_undo_migration,
32
37
  PathAttributeName,
33
38
  UnauthorisedError,
34
39
  } from '../../defs.js';
40
+ import { saveMigration } from '../../modules/core.js';
41
+ import { getAppSpec } from '../../loader.js';
35
42
 
36
43
  export let defaultDataSource: DataSource | undefined;
37
44
 
@@ -165,9 +172,10 @@ function mkDbName(): string {
165
172
 
166
173
  function makePostgresDataSource(
167
174
  entities: EntitySchema[],
168
- config: DatabaseConfig | undefined,
169
- synchronize: boolean = true
175
+ config: DatabaseConfig | undefined
170
176
  ): DataSource {
177
+ const synchronize = isRuntimeMode_dev() || isRuntimeMode_init_schema();
178
+ //const runMigrations = isRuntimeMode_migration() || isRuntimeMode_undo_migration() || !synchronize;
171
179
  return new DataSource({
172
180
  type: 'postgres',
173
181
  host: process.env.POSTGRES_HOST || config?.host || 'localhost',
@@ -176,6 +184,8 @@ function makePostgresDataSource(
176
184
  password: process.env.POSTGRES_PASSWORD || config?.password || 'postgres',
177
185
  database: process.env.POSTGRES_DB || config?.dbname || 'postgres',
178
186
  synchronize: synchronize,
187
+ migrationsRun: false,
188
+ dropSchema: false,
179
189
  entities: entities,
180
190
  invalidWhereValuesBehavior: {
181
191
  null: 'sql-null',
@@ -195,14 +205,17 @@ function getPostgressEnvPort(): number | undefined {
195
205
 
196
206
  function makeSqliteDataSource(
197
207
  entities: EntitySchema[],
198
- config: DatabaseConfig | undefined,
199
- synchronize: boolean = true
208
+ config: DatabaseConfig | undefined
200
209
  ): DataSource {
210
+ const synchronize = isRuntimeMode_dev() || isRuntimeMode_init_schema();
211
+ //const runMigrations = isRuntimeMode_migration() || isRuntimeMode_undo_migration() || !synchronize;
201
212
  return new DataSource({
202
213
  type: 'sqlite',
203
214
  database: config?.dbname || mkDbName(),
204
215
  synchronize: synchronize,
205
216
  entities: entities,
217
+ migrationsRun: false,
218
+ dropSchema: false,
206
219
  invalidWhereValuesBehavior: {
207
220
  null: 'sql-null',
208
221
  undefined: 'ignore',
@@ -210,6 +223,47 @@ function makeSqliteDataSource(
210
223
  });
211
224
  }
212
225
 
226
+ async function execMigrationSql(dataSource: DataSource, sql: string[]) {
227
+ const queryRunner = dataSource.createQueryRunner();
228
+ await queryRunner.startTransaction();
229
+ for (let i = 0; i < sql.length; ++i) {
230
+ await queryRunner.query(sql[i]);
231
+ }
232
+ await queryRunner.commitTransaction();
233
+ }
234
+
235
+ async function maybeHandleMigrations(dataSource: DataSource) {
236
+ const is_migration = isRuntimeMode_migration();
237
+ const is_undo_migration = isRuntimeMode_undo_migration();
238
+ const is_gen_migration = isRuntimeMode_generate_migration();
239
+ if (is_migration || is_undo_migration || is_gen_migration) {
240
+ const sqlInMemory = await dataSource.driver.createSchemaBuilder().log();
241
+ let ups: string[] | undefined;
242
+ if (is_migration || is_gen_migration) {
243
+ ups = new Array<string>();
244
+ sqlInMemory.upQueries.forEach(upQuery => {
245
+ ups?.push(upQuery.query.replaceAll('`', '\\`'));
246
+ });
247
+ }
248
+ let downs: string[] | undefined;
249
+ if (is_undo_migration || is_gen_migration) {
250
+ downs = new Array<string>();
251
+ sqlInMemory.downQueries.forEach(downQuery => {
252
+ downs?.push(downQuery.query.replaceAll('`', '\\`'));
253
+ });
254
+ }
255
+ if (is_migration && ups?.length) {
256
+ await saveMigration(getAppSpec().version, ups, downs);
257
+ await execMigrationSql(dataSource, ups);
258
+ } else if (is_undo_migration && downs?.length) {
259
+ await saveMigration(getAppSpec().version, ups, downs);
260
+ await execMigrationSql(dataSource, downs);
261
+ } else if (is_gen_migration) {
262
+ await saveMigration(getAppSpec().version, ups, downs);
263
+ }
264
+ }
265
+ }
266
+
213
267
  function isBrowser(): boolean {
214
268
  // window for DOM pages, self+importScripts for web workers
215
269
  return (
@@ -305,6 +359,7 @@ export async function initDatabase(config: DatabaseConfig | undefined) {
305
359
  const ormScm = modulesAsOrmSchema();
306
360
  defaultDataSource = mkds(ormScm.entities, config) as DataSource;
307
361
  await defaultDataSource.initialize();
362
+ await maybeHandleMigrations(defaultDataSource);
308
363
  if (ormScm.fkSpecs.length > 0) {
309
364
  const qr = defaultDataSource.createQueryRunner();
310
365
  for (let i = 0; i < ormScm.fkSpecs.length; ++i) {
@@ -316,7 +371,11 @@ export async function initDatabase(config: DatabaseConfig | undefined) {
316
371
  onDelete: fk.onDelete,
317
372
  onUpdate: fk.onUpdate,
318
373
  });
319
- await qr.createForeignKey(asTableReference(fk.moduleName, fk.entityName), fkobj);
374
+ try {
375
+ await qr.createForeignKey(asTableReference(fk.moduleName, fk.entityName), fkobj);
376
+ } catch (reason: any) {
377
+ logger.warn(`initDatabase: ${reason}`);
378
+ }
320
379
  }
321
380
  }
322
381
  const vectEnts = ormScm.vectorEntities.map((es: EntitySchema) => {