meadow-integration 1.1.1 → 1.1.2

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.
@@ -9,8 +9,8 @@
9
9
  # - One step to wire: GITHUB_TOKEN already has `packages: write`
10
10
  #
11
11
  # Image lands at:
12
- # ghcr.io/stevenvelozo/meadow-integration:<version>
13
- # ghcr.io/stevenvelozo/meadow-integration:latest (only on the
12
+ # ghcr.io/fable-retold/meadow-integration:<version>
13
+ # ghcr.io/fable-retold/meadow-integration:latest (only on the
14
14
  # highest stable tag)
15
15
  #
16
16
  # Manual run: `Actions → Publish container image → Run workflow`,
@@ -170,7 +170,7 @@ postpublish: BUILD_DOCKER=1 → tag + push ← image trigger
170
170
  ↓ (tag arrives at GitHub)
171
171
  .github/workflows/publish-image.yml fires:
172
172
  - docker buildx build --platform linux/amd64,linux/arm64
173
- - docker push ghcr.io/stevenvelozo/meadow-integration:<version>
173
+ - docker push ghcr.io/fable-retold/meadow-integration:<version>
174
174
  - tags: <version>, <major>.<minor>, <major>, latest
175
175
  ```
176
176
 
@@ -189,14 +189,14 @@ After `release:patch` completes:
189
189
  2. **GHCR workflow**: visit
190
190
  `https://github.com/fable-retold/meadow-integration/actions` and
191
191
  confirm the "Publish container image" run succeeded.
192
- 3. **Image**: `docker pull ghcr.io/stevenvelozo/meadow-integration:<version>`
192
+ 3. **Image**: `docker pull ghcr.io/fable-retold/meadow-integration:<version>`
193
193
  should succeed. The image is also tagged as `latest`, `<major>`, and
194
194
  `<major>.<minor>`.
195
195
  4. **Smoke test** (one-shot — runs the bundled BookStore default schema
196
196
  against a non-existent API; expect it to fail at the data-fetch step,
197
197
  which proves the binary started correctly):
198
198
  ```bash
199
- docker run --rm ghcr.io/stevenvelozo/meadow-integration:latest
199
+ docker run --rm ghcr.io/fable-retold/meadow-integration:latest
200
200
  ```
201
201
 
202
202
  ---
@@ -280,7 +280,7 @@ whatever stability level fits.
280
280
  ### Pull and run (one-shot)
281
281
 
282
282
  ```bash
283
- docker pull ghcr.io/stevenvelozo/meadow-integration:latest
283
+ docker pull ghcr.io/fable-retold/meadow-integration:latest
284
284
 
285
285
  # Single sync run; container exits when done
286
286
  docker run --rm \
@@ -293,7 +293,7 @@ docker run --rm \
293
293
  -e MEADOW_INTEGRATION_DB_NAME=mydatabase \
294
294
  -e MEADOW_INTEGRATION_SCHEMA_PATH=/schemas/my-schema.json \
295
295
  -v $(pwd)/schemas:/schemas:ro \
296
- ghcr.io/stevenvelozo/meadow-integration:latest
296
+ ghcr.io/fable-retold/meadow-integration:latest
297
297
  ```
298
298
 
299
299
  ### Configuration via env vars
package/CONTRIBUTING.md CHANGED
@@ -47,4 +47,4 @@ When in doubt, match what the surrounding code does.
47
47
 
48
48
  ## Repository Structure
49
49
 
50
- Each module is its own git repository. The [retold](https://github.com/stevenvelozo/retold) repository tracks module organization but does not contain module source code. Direct your pull request to the specific module repository where your change belongs.
50
+ Each module is its own git repository. The [retold](https://github.com/fable-retold/retold) repository tracks module organization but does not contain module source code. Direct your pull request to the specific module repository where your change belongs.
package/README.md CHANGED
@@ -361,4 +361,4 @@ MIT
361
361
 
362
362
  ## Contributing
363
363
 
364
- Pull requests are welcome. For details on our code of conduct, contribution process, and testing requirements, see the [Retold Contributing Guide](https://github.com/stevenvelozo/retold/blob/main/docs/contributing.md).
364
+ Pull requests are welcome. For details on our code of conduct, contribution process, and testing requirements, see the [Retold Contributing Guide](https://github.com/fable-retold/retold/blob/main/docs/contributing.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meadow-integration",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Meadow Data Integration",
5
5
  "retoldBeacon": {
6
6
  "displayName": "Meadow Integration",
@@ -60,7 +60,8 @@
60
60
  "devDependencies": {
61
61
  "meadow-connection-sqlite": "^1.0.20",
62
62
  "pict-docuserve": "^1.4.19",
63
- "quackage": "^1.3.0"
63
+ "quackage": "^1.3.0",
64
+ "retold-harness": "^1.1.13"
64
65
  },
65
66
  "mocha": {
66
67
  "diff": true,
@@ -546,8 +546,16 @@ class MeadowSyncEntityInitial extends libFableServiceProviderBase
546
546
  const fProcessPageRecords = (pBody, fPageProcessComplete) =>
547
547
  {
548
548
  this.fable.Utility.eachLimit(pBody, 5,
549
- (pEntityRecord, fEntitySyncComplete) =>
549
+ (pEntityRecord, fRawEntitySyncComplete) =>
550
550
  {
551
+ // node:sqlite resolves its callbacks synchronously, so the
552
+ // per-record Meadow behavior chain (doCreate -> CollisionRename
553
+ // -> doRead, threaded through async.waterfall/eachOfLimit) never
554
+ // yields and the call stack grows with the record count until it
555
+ // overflows on large syncs. Defer each record's completion so the
556
+ // stack unwinds between records — the same guard the advanced-ID
557
+ // pagination path applies with setImmediate(fFetchPage) below.
558
+ const fEntitySyncComplete = (pError) => setImmediate(() => fRawEntitySyncComplete(pError));
551
559
  const tmpRecord = pEntityRecord;
552
560
  const tmpQuery = this.Meadow.query;
553
561
 
@@ -23,6 +23,33 @@ function valueTemplate(pColumn)
23
23
  return `{~D:Record.${pColumn}~}`;
24
24
  }
25
25
 
26
+ /**
27
+ * The value template for an entity's OWN key segment — the combinatorial-GUID input. Three shapes, in
28
+ * precedence order: a user-typed pict template `OwnKeyTemplate` (any expression, e.g.
29
+ * `{~D:Record.A~}-{~D:Record.B~}`); multiple columns `OwnKeyColumns` concatenated into one segment; or a
30
+ * single `OwnKeyColumn` (the original behavior). The transform resolves the whole string per row via
31
+ * fable's parseTemplate, so multiple `{~D:Record.X~}` tags in one segment just work.
32
+ * @param {Record<string, any>} pEntityConfig
33
+ * @returns {string}
34
+ */
35
+ function ownValueTemplate(pEntityConfig)
36
+ {
37
+ const tmpConfig = pEntityConfig || {};
38
+ if (tmpConfig.OwnKeyTemplate)
39
+ {
40
+ return String(tmpConfig.OwnKeyTemplate);
41
+ }
42
+ if (Array.isArray(tmpConfig.OwnKeyColumns) && (tmpConfig.OwnKeyColumns.length > 0))
43
+ {
44
+ return tmpConfig.OwnKeyColumns.map((pColumn) => valueTemplate(pColumn)).join('');
45
+ }
46
+ if (tmpConfig.OwnKeyColumn)
47
+ {
48
+ return valueTemplate(tmpConfig.OwnKeyColumn);
49
+ }
50
+ return '';
51
+ }
52
+
26
53
  /** Derive a short uppercase abbreviation for an entity with no catalog entry (initials / first letters). */
27
54
  function _deriveAbbreviation(pEntityName)
28
55
  {
@@ -108,11 +135,12 @@ function _ownSegments(pEntityName, pEntityConfig, pContext)
108
135
  tmpSegments.push({ abbrev: abbreviationFor(pParentEntity, pContext), valueTemplate: tmpKeyColumn ? valueTemplate(tmpKeyColumn) : '' });
109
136
  });
110
137
 
111
- if (!pEntityConfig.OwnKeyColumn)
138
+ const tmpOwnValue = ownValueTemplate(pEntityConfig);
139
+ if (!tmpOwnValue)
112
140
  {
113
- tmpWarnings.push(`"${pEntityName}" has no own-key column mapped — its GUID cannot be stable, so re-imports will create duplicates. Map a natural-key column (e.g. a code).`);
141
+ tmpWarnings.push(`"${pEntityName}" has no own-key column mapped — its GUID cannot be stable, so re-imports will create duplicates. Map a natural-key column (e.g. a code), several columns, or a template.`);
114
142
  }
115
- tmpSegments.push({ abbrev: abbreviationFor(pEntityName, pContext), valueTemplate: pEntityConfig.OwnKeyColumn ? valueTemplate(pEntityConfig.OwnKeyColumn) : '' });
143
+ tmpSegments.push({ abbrev: abbreviationFor(pEntityName, pContext), valueTemplate: tmpOwnValue });
116
144
 
117
145
  return { segments: tmpSegments, warnings: tmpWarnings };
118
146
  }
@@ -252,4 +280,5 @@ module.exports = {
252
280
  joinFieldName,
253
281
  abbreviationFor,
254
282
  valueTemplate,
283
+ ownValueTemplate,
255
284
  };
@@ -10,6 +10,7 @@
10
10
  const Chai = require('chai');
11
11
  const Expect = Chai.expect;
12
12
 
13
+ const libPath = require('path');
13
14
  const libFable = require('fable');
14
15
  const libMeadowConnectionSQLite = require('meadow-connection-sqlite');
15
16
  const libRetoldDataService = require('retold-data-service');
@@ -291,7 +292,7 @@ suite
291
292
  _Fable.serviceManager.addServiceType('RetoldDataService', libRetoldDataService);
292
293
  _RetoldDataService = _Fable.serviceManager.instantiateServiceProvider('RetoldDataService',
293
294
  {
294
- FullMeadowSchemaPath: `${__dirname}/../node_modules/retold-harness/source/schemas/bookstore/`,
295
+ FullMeadowSchemaPath: `${libPath.dirname(require.resolve('retold-harness/source/schemas/bookstore/Schema.json'))}/`,
295
296
  FullMeadowSchemaFilename: `Schema.json`,
296
297
 
297
298
  StorageProvider: 'SQLite',
@@ -62,6 +62,29 @@ suite
62
62
  }
63
63
  );
64
64
  test
65
+ (
66
+ 'combinatorial own key: multiple columns concatenate into one own segment',
67
+ () =>
68
+ {
69
+ const tmpConfig = { Prefix: 'UI', Entities: { Project: { Mode: 'prefixed', OwnKeyColumns: [ 'District', 'ProjectCode' ] } } };
70
+ const tmpProject = libStrategy.compile(tmpConfig, { Catalog: CATALOG, SchemaSizes: SIZES }).Strategies.Project;
71
+ Expect(tmpProject.Own.Compose.segments).to.have.length(1);
72
+ Expect(tmpProject.Own.Compose.segments[0].valueTemplate).to.equal('{~D:Record.District~}{~D:Record.ProjectCode~}');
73
+ }
74
+ );
75
+ test
76
+ (
77
+ 'combinatorial own key: a user-typed pict template becomes the own segment verbatim (and wins over a single column)',
78
+ () =>
79
+ {
80
+ const tmpConfig = { Prefix: 'UI', Entities: { Project: { Mode: 'prefixed', OwnKeyTemplate: '{~D:Record.District~}-{~D:Record.ProjectCode~}' } } };
81
+ const tmpProject = libStrategy.compile(tmpConfig, { Catalog: CATALOG, SchemaSizes: SIZES }).Strategies.Project;
82
+ Expect(tmpProject.Own.Compose.segments[0].valueTemplate).to.equal('{~D:Record.District~}-{~D:Record.ProjectCode~}');
83
+ const tmpBoth = libStrategy.compile({ Prefix: 'UI', Entities: { Project: { OwnKeyColumn: 'X', OwnKeyTemplate: 'T{~D:Record.Y~}' } } }, { Catalog: CATALOG }).Strategies.Project;
84
+ Expect(tmpBoth.Own.Compose.segments[0].valueTemplate).to.equal('T{~D:Record.Y~}');
85
+ }
86
+ );
87
+ test
65
88
  (
66
89
  'composes the headline LineItem own GUID from context + own segments',
67
90
  () =>