@meadow-studio/platform 0.0.0-stage → 0.1.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/MeadowPlatform.podspec +17 -0
- package/Package.swift +23 -0
- package/README.md +147 -2
- package/android/build.gradle +42 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/app/meadow/platform/MeadowAppRatingPlugin.java +38 -0
- package/dist/cli/commands.d.ts +74 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +1062 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/config.d.ts +123 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +428 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/meadow.d.ts +8 -0
- package/dist/cli/meadow.d.ts.map +1 -0
- package/dist/cli/meadow.js +463 -0
- package/dist/cli/meadow.js.map +1 -0
- package/dist/cli/recipes.d.ts +10 -0
- package/dist/cli/recipes.d.ts.map +1 -0
- package/dist/cli/recipes.js +575 -0
- package/dist/cli/recipes.js.map +1 -0
- package/dist/cli/sync.d.ts +14 -0
- package/dist/cli/sync.d.ts.map +1 -0
- package/dist/cli/sync.js +310 -0
- package/dist/cli/sync.js.map +1 -0
- package/dist/cli/templates.d.ts +27 -0
- package/dist/cli/templates.d.ts.map +1 -0
- package/dist/cli/templates.js +1246 -0
- package/dist/cli/templates.js.map +1 -0
- package/dist/cli/website.d.ts +5 -0
- package/dist/cli/website.d.ts.map +1 -0
- package/dist/cli/website.js +262 -0
- package/dist/cli/website.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/notifications.d.ts +119 -0
- package/dist/notifications.d.ts.map +1 -0
- package/dist/notifications.js +320 -0
- package/dist/notifications.js.map +1 -0
- package/dist/platform.d.ts +163 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +623 -0
- package/dist/platform.js.map +1 -0
- package/dist/purchases.d.ts +88 -0
- package/dist/purchases.d.ts.map +1 -0
- package/dist/purchases.js +296 -0
- package/dist/purchases.js.map +1 -0
- package/dist/ui/app-build.component.d.ts +18 -0
- package/dist/ui/app-build.component.d.ts.map +1 -0
- package/dist/ui/app-build.component.js +56 -0
- package/dist/ui/app-build.component.js.map +1 -0
- package/dist/ui/app-build.model.d.ts +11 -0
- package/dist/ui/app-build.model.d.ts.map +1 -0
- package/dist/ui/app-build.model.js +18 -0
- package/dist/ui/app-build.model.js.map +1 -0
- package/dist/ui/app-links.component.d.ts +14 -0
- package/dist/ui/app-links.component.d.ts.map +1 -0
- package/dist/ui/app-links.component.js +77 -0
- package/dist/ui/app-links.component.js.map +1 -0
- package/dist/ui/app-links.model.d.ts +18 -0
- package/dist/ui/app-links.model.d.ts.map +1 -0
- package/dist/ui/app-links.model.js +17 -0
- package/dist/ui/app-links.model.js.map +1 -0
- package/dist/ui/index.d.ts +5 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +5 -0
- package/dist/ui/index.js.map +1 -0
- package/docs/CLI.md +376 -0
- package/docs/PLATFORM_GUIDE.md +422 -0
- package/docs/POC_FINDINGS.md +218 -0
- package/docs/PROJECT_BRIEF.md +501 -0
- package/docs/RELEASING.md +49 -0
- package/docs/V0.1_PATH.md +310 -0
- package/ios/Sources/MeadowPlatformPlugin/MeadowAppRatingPlugin.swift +39 -0
- package/package.json +89 -4
- package/schema/meadow-config.schema.json +250 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
import { chmodSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { readProjectConfig, writeProjectConfig } from './config.js';
|
|
4
|
+
import { syncProject } from './sync.js';
|
|
5
|
+
import { TOOLCHAIN } from './templates.js';
|
|
6
|
+
export const LOCAL_DATA_PACKAGE_VERSION = TOOLCHAIN.localData;
|
|
7
|
+
export const JEEP_SQLITE_PACKAGE_VERSION = TOOLCHAIN.jeepSqlite;
|
|
8
|
+
// jeep-sqlite 2.8.0 was built against this ABI. Newer sql.js WASM files can
|
|
9
|
+
// install under its caret range but fail at runtime with incompatible imports.
|
|
10
|
+
export const SQL_JS_PACKAGE_VERSION = TOOLCHAIN.sqlJs;
|
|
11
|
+
export const LOCAL_DATA_DATABASE_VERSION = 1;
|
|
12
|
+
export const LOCAL_NOTIFICATIONS_PACKAGE_VERSION = TOOLCHAIN.localNotifications;
|
|
13
|
+
const LOCAL_DATA_ROOT = 'src/app/data/local-data';
|
|
14
|
+
const LOCAL_DATA_PENDING = `${LOCAL_DATA_ROOT}/.meadow-recipe-pending`;
|
|
15
|
+
export function addNotifications(directory, runner) {
|
|
16
|
+
const root = resolve(directory);
|
|
17
|
+
const config = readProjectConfig(root);
|
|
18
|
+
if (config.services.notifications.enabled && config.services.notifications.configured) {
|
|
19
|
+
throw new Error('Notifications are already enabled in meadow.config.json.');
|
|
20
|
+
}
|
|
21
|
+
writeProjectConfig({
|
|
22
|
+
...config,
|
|
23
|
+
services: {
|
|
24
|
+
...config.services,
|
|
25
|
+
notifications: { enabled: true, configured: false },
|
|
26
|
+
},
|
|
27
|
+
}, root);
|
|
28
|
+
const prepared = syncProject(root);
|
|
29
|
+
if (!prepared.valid) {
|
|
30
|
+
throw new Error('Notifications were enabled, but generated files could not be synchronized.');
|
|
31
|
+
}
|
|
32
|
+
runner.run('npm', ['install'], root);
|
|
33
|
+
runner.run('npm', ['run', 'build:device'], root);
|
|
34
|
+
for (const platform of config.app.platforms) {
|
|
35
|
+
if (existsSync(resolve(root, platform))) {
|
|
36
|
+
runner.run(resolve(root, 'node_modules/.bin/cap'), ['sync', platform], root);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
writeProjectConfig({
|
|
41
|
+
...config,
|
|
42
|
+
services: {
|
|
43
|
+
...config.services,
|
|
44
|
+
notifications: { enabled: true, configured: true },
|
|
45
|
+
},
|
|
46
|
+
}, root);
|
|
47
|
+
const completed = syncProject(root);
|
|
48
|
+
if (!completed.valid) {
|
|
49
|
+
throw new Error('Notifications were installed, but generated files could not be synchronized.');
|
|
50
|
+
}
|
|
51
|
+
runner.run('npm', ['run', 'format'], root);
|
|
52
|
+
runner.run('npm', ['run', 'check'], root);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
writeProjectConfig({
|
|
56
|
+
...config,
|
|
57
|
+
services: {
|
|
58
|
+
...config.services,
|
|
59
|
+
notifications: { enabled: true, configured: false },
|
|
60
|
+
},
|
|
61
|
+
}, root);
|
|
62
|
+
syncProject(root);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
return [
|
|
66
|
+
'Local notifications added with explicit permission handling and safe native fallbacks.',
|
|
67
|
+
'Keep notification copy, identifiers, schedules, channels, tap navigation, and analytics in the application.',
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
export function addLocalData(directory, runner) {
|
|
71
|
+
const root = resolve(directory);
|
|
72
|
+
const config = readProjectConfig(root);
|
|
73
|
+
const managedFiles = localDataFiles(config.app.slug);
|
|
74
|
+
if (config.services.localData.enabled) {
|
|
75
|
+
throw new Error('Local data is already enabled in meadow.config.json.');
|
|
76
|
+
}
|
|
77
|
+
const pendingPath = resolve(root, LOCAL_DATA_PENDING);
|
|
78
|
+
const resuming = existsSync(pendingPath);
|
|
79
|
+
const existing = [...managedFiles.keys()].find((path) => existsSync(resolve(root, path)));
|
|
80
|
+
if (existing && !resuming) {
|
|
81
|
+
throw new Error(`Local data files already exist outside a Meadow recipe (${existing}).`);
|
|
82
|
+
}
|
|
83
|
+
mkdirSync(dirname(pendingPath), { recursive: true });
|
|
84
|
+
writeFileSync(pendingPath, 'This file allows an interrupted Meadow local-data recipe to resume.\n');
|
|
85
|
+
const packagePath = resolve(root, 'package.json');
|
|
86
|
+
if (!existsSync(packagePath))
|
|
87
|
+
throw new Error('package.json is missing.');
|
|
88
|
+
const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
89
|
+
packageJson.dependencies = {
|
|
90
|
+
...packageJson.dependencies,
|
|
91
|
+
'@capacitor-community/sqlite': LOCAL_DATA_PACKAGE_VERSION,
|
|
92
|
+
'jeep-sqlite': JEEP_SQLITE_PACKAGE_VERSION,
|
|
93
|
+
'sql.js': SQL_JS_PACKAGE_VERSION,
|
|
94
|
+
};
|
|
95
|
+
writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
96
|
+
for (const [path, contents] of managedFiles) {
|
|
97
|
+
const target = resolve(root, path);
|
|
98
|
+
if (resuming && existsSync(target))
|
|
99
|
+
continue;
|
|
100
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
101
|
+
writeFileSync(target, contents);
|
|
102
|
+
}
|
|
103
|
+
runner.run('npm', ['install'], root);
|
|
104
|
+
copySqliteWasm(root);
|
|
105
|
+
configureAndroidBackups(root);
|
|
106
|
+
updateReleaseChecklist(root);
|
|
107
|
+
runner.run(resolve(root, 'node_modules/.bin/cap'), ['sync'], root);
|
|
108
|
+
runner.run('npm', ['run', 'format'], root);
|
|
109
|
+
runner.run('npm', ['run', 'check'], root);
|
|
110
|
+
writeProjectConfig({
|
|
111
|
+
...config,
|
|
112
|
+
services: {
|
|
113
|
+
...config.services,
|
|
114
|
+
localData: {
|
|
115
|
+
enabled: true,
|
|
116
|
+
configured: true,
|
|
117
|
+
databaseVersion: LOCAL_DATA_DATABASE_VERSION,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
}, root);
|
|
121
|
+
const sync = syncProject(root);
|
|
122
|
+
if (!sync.valid) {
|
|
123
|
+
throw new Error('Local data was added, but generated files could not be synchronized.');
|
|
124
|
+
}
|
|
125
|
+
rmSync(pendingPath);
|
|
126
|
+
return [
|
|
127
|
+
'Local SQLite data foundation added for iOS, Android, and IndexedDB-backed web development.',
|
|
128
|
+
`Start with ${LOCAL_DATA_ROOT}/README.md and keep domain repositories in the application.`,
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
export function localDataFiles(slug) {
|
|
132
|
+
return new Map([
|
|
133
|
+
[`${LOCAL_DATA_ROOT}/local-data.types.ts`, localDataTypes()],
|
|
134
|
+
[`${LOCAL_DATA_ROOT}/local-data.migrations.ts`, localDataMigrations()],
|
|
135
|
+
[`${LOCAL_DATA_ROOT}/local-database.ts`, localDatabase(slug)],
|
|
136
|
+
[`${LOCAL_DATA_ROOT}/local-document.store.ts`, localDocumentStore()],
|
|
137
|
+
[`${LOCAL_DATA_ROOT}/local-document.store.spec.ts`, localDocumentStoreSpec()],
|
|
138
|
+
[`${LOCAL_DATA_ROOT}/README.md`, localDataReadme()],
|
|
139
|
+
]);
|
|
140
|
+
}
|
|
141
|
+
function copySqliteWasm(root) {
|
|
142
|
+
const source = resolve(root, 'node_modules/sql.js/dist/sql-wasm.wasm');
|
|
143
|
+
if (!existsSync(source)) {
|
|
144
|
+
throw new Error('SQLite web runtime is missing sql-wasm.wasm after npm install.');
|
|
145
|
+
}
|
|
146
|
+
const target = resolve(root, 'public/assets/sql-wasm.wasm');
|
|
147
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
148
|
+
cpSync(source, target);
|
|
149
|
+
chmodSync(target, 0o644);
|
|
150
|
+
}
|
|
151
|
+
function configureAndroidBackups(root) {
|
|
152
|
+
const manifestPath = resolve(root, 'android/app/src/main/AndroidManifest.xml');
|
|
153
|
+
if (!existsSync(manifestPath))
|
|
154
|
+
return;
|
|
155
|
+
let manifest = readFileSync(manifestPath, 'utf8');
|
|
156
|
+
manifest = manifest.replace(/android:allowBackup="[^"]*"/, 'android:allowBackup="false"');
|
|
157
|
+
if (!manifest.includes('android:fullBackupContent=')) {
|
|
158
|
+
manifest = manifest.replace('<application', '<application\n android:fullBackupContent="false"');
|
|
159
|
+
}
|
|
160
|
+
if (!manifest.includes('android:dataExtractionRules=')) {
|
|
161
|
+
manifest = manifest.replace('<application', '<application\n android:dataExtractionRules="@xml/data_extraction_rules"');
|
|
162
|
+
}
|
|
163
|
+
writeFileSync(manifestPath, manifest);
|
|
164
|
+
const rulesPath = resolve(root, 'android/app/src/main/res/xml/data_extraction_rules.xml');
|
|
165
|
+
mkdirSync(dirname(rulesPath), { recursive: true });
|
|
166
|
+
writeFileSync(rulesPath, `<?xml version="1.0" encoding="utf-8"?>
|
|
167
|
+
<data-extraction-rules>
|
|
168
|
+
<cloud-backup>
|
|
169
|
+
<exclude domain="root" />
|
|
170
|
+
<exclude domain="database" />
|
|
171
|
+
<exclude domain="sharedpref" />
|
|
172
|
+
<exclude domain="external" />
|
|
173
|
+
</cloud-backup>
|
|
174
|
+
<device-transfer>
|
|
175
|
+
<exclude domain="root" />
|
|
176
|
+
<exclude domain="database" />
|
|
177
|
+
<exclude domain="sharedpref" />
|
|
178
|
+
<exclude domain="external" />
|
|
179
|
+
</device-transfer>
|
|
180
|
+
</data-extraction-rules>
|
|
181
|
+
`);
|
|
182
|
+
}
|
|
183
|
+
function updateReleaseChecklist(root) {
|
|
184
|
+
const path = resolve(root, 'docs/RELEASE_CHECKLIST.md');
|
|
185
|
+
if (!existsSync(path))
|
|
186
|
+
return;
|
|
187
|
+
const source = readFileSync(path, 'utf8');
|
|
188
|
+
const marker = '- [ ] Test local-data migrations from both a fresh install and the previous released version.';
|
|
189
|
+
if (source.includes(marker))
|
|
190
|
+
return;
|
|
191
|
+
const section = `
|
|
192
|
+
## Local data
|
|
193
|
+
|
|
194
|
+
${marker}
|
|
195
|
+
- [ ] Verify account deletion and reset remove every app-owned local table.
|
|
196
|
+
- [ ] Review the Android backup exclusions against the app's restore requirements.
|
|
197
|
+
- [ ] Review native SQLCipher export-compliance declarations for both stores.
|
|
198
|
+
`;
|
|
199
|
+
writeFileSync(path, `${source.trimEnd()}\n${section}`);
|
|
200
|
+
}
|
|
201
|
+
function localDataTypes() {
|
|
202
|
+
return `export type LocalDataValue = string | number | null | Uint8Array;
|
|
203
|
+
|
|
204
|
+
export interface LocalDataMigration {
|
|
205
|
+
readonly toVersion: number;
|
|
206
|
+
readonly statements: readonly string[];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface LocalDataRunResult {
|
|
210
|
+
readonly changes: number;
|
|
211
|
+
readonly lastId: number | null;
|
|
212
|
+
}
|
|
213
|
+
`;
|
|
214
|
+
}
|
|
215
|
+
function localDataMigrations() {
|
|
216
|
+
return `import type { LocalDataMigration } from './local-data.types';
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Append migrations in order and never edit one that has shipped. The database
|
|
220
|
+
* version is derived from the final migration, so adding version 2 is enough to
|
|
221
|
+
* upgrade existing installations on their next database open.
|
|
222
|
+
*/
|
|
223
|
+
export const LOCAL_DATA_MIGRATIONS = [
|
|
224
|
+
{
|
|
225
|
+
toVersion: 1,
|
|
226
|
+
statements: [
|
|
227
|
+
\`CREATE TABLE IF NOT EXISTS meadow_documents (
|
|
228
|
+
collection TEXT NOT NULL,
|
|
229
|
+
key TEXT NOT NULL,
|
|
230
|
+
value TEXT NOT NULL,
|
|
231
|
+
updated_at INTEGER NOT NULL,
|
|
232
|
+
PRIMARY KEY (collection, key)
|
|
233
|
+
);\`,
|
|
234
|
+
'CREATE INDEX IF NOT EXISTS meadow_documents_updated ON meadow_documents(collection, updated_at DESC);',
|
|
235
|
+
],
|
|
236
|
+
},
|
|
237
|
+
] as const satisfies readonly LocalDataMigration[];
|
|
238
|
+
|
|
239
|
+
export const LOCAL_DATA_VERSION =
|
|
240
|
+
LOCAL_DATA_MIGRATIONS[LOCAL_DATA_MIGRATIONS.length - 1]?.toVersion ?? 0;
|
|
241
|
+
|
|
242
|
+
export function assertLocalDataMigrations(
|
|
243
|
+
migrations: readonly LocalDataMigration[] = LOCAL_DATA_MIGRATIONS,
|
|
244
|
+
): void {
|
|
245
|
+
if (!migrations.length) throw new Error('At least one local-data migration is required.');
|
|
246
|
+
migrations.forEach((migration, index) => {
|
|
247
|
+
const expected = index + 1;
|
|
248
|
+
if (migration.toVersion !== expected) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
\`Local-data migrations must be sequential; expected version \${expected}.\`,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
if (!migration.statements.length || migration.statements.some((statement) => !statement.trim())) {
|
|
254
|
+
throw new Error(\`Local-data migration \${expected} must contain SQL statements.\`);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
`;
|
|
259
|
+
}
|
|
260
|
+
function localDatabase(slug) {
|
|
261
|
+
return `import { Injectable } from '@angular/core';
|
|
262
|
+
import { Capacitor } from '@capacitor/core';
|
|
263
|
+
import {
|
|
264
|
+
CapacitorSQLite,
|
|
265
|
+
SQLiteConnection,
|
|
266
|
+
type SQLiteDBConnection,
|
|
267
|
+
} from '@capacitor-community/sqlite';
|
|
268
|
+
import { defineCustomElements } from 'jeep-sqlite/loader';
|
|
269
|
+
import {
|
|
270
|
+
assertLocalDataMigrations,
|
|
271
|
+
LOCAL_DATA_MIGRATIONS,
|
|
272
|
+
LOCAL_DATA_VERSION,
|
|
273
|
+
} from './local-data.migrations';
|
|
274
|
+
import type { LocalDataRunResult, LocalDataValue } from './local-data.types';
|
|
275
|
+
|
|
276
|
+
const DATABASE_NAME = ${JSON.stringify(slug)};
|
|
277
|
+
|
|
278
|
+
@Injectable({ providedIn: 'root' })
|
|
279
|
+
export class LocalDatabase {
|
|
280
|
+
private readonly sqlite = new SQLiteConnection(CapacitorSQLite);
|
|
281
|
+
private initialization?: Promise<SQLiteDBConnection>;
|
|
282
|
+
private transactionActive = false;
|
|
283
|
+
|
|
284
|
+
ready(): Promise<SQLiteDBConnection> {
|
|
285
|
+
this.initialization ??= this.initialize().catch((error: unknown) => {
|
|
286
|
+
this.initialization = undefined;
|
|
287
|
+
throw error;
|
|
288
|
+
});
|
|
289
|
+
return this.initialization;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async query<T extends object>(
|
|
293
|
+
statement: string,
|
|
294
|
+
values: readonly LocalDataValue[] = [],
|
|
295
|
+
): Promise<readonly T[]> {
|
|
296
|
+
const result = await (await this.ready()).query(statement, [...values]);
|
|
297
|
+
return (result.values ?? []) as T[];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async run(
|
|
301
|
+
statement: string,
|
|
302
|
+
values: readonly LocalDataValue[] = [],
|
|
303
|
+
): Promise<LocalDataRunResult> {
|
|
304
|
+
const result = await (await this.ready()).run(statement, [...values]);
|
|
305
|
+
await this.persistWebAfterWrite();
|
|
306
|
+
return {
|
|
307
|
+
changes: result.changes?.changes ?? 0,
|
|
308
|
+
lastId: result.changes?.lastId ?? null,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async execute(statements: string): Promise<number> {
|
|
313
|
+
const result = await (await this.ready()).execute(statements);
|
|
314
|
+
await this.persistWebAfterWrite();
|
|
315
|
+
return result.changes?.changes ?? 0;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async transaction<T>(operation: (database: LocalDatabase) => Promise<T>): Promise<T> {
|
|
319
|
+
if (this.transactionActive) throw new Error('Nested local-data transactions are not supported.');
|
|
320
|
+
const connection = await this.ready();
|
|
321
|
+
await connection.beginTransaction();
|
|
322
|
+
this.transactionActive = true;
|
|
323
|
+
let committed = false;
|
|
324
|
+
try {
|
|
325
|
+
const result = await operation(this);
|
|
326
|
+
await connection.commitTransaction();
|
|
327
|
+
committed = true;
|
|
328
|
+
return result;
|
|
329
|
+
} catch (error) {
|
|
330
|
+
try {
|
|
331
|
+
const active = await connection.isTransactionActive();
|
|
332
|
+
if (active.result) await connection.rollbackTransaction();
|
|
333
|
+
} catch {
|
|
334
|
+
// Preserve the operation error; a future call will reopen after an app restart.
|
|
335
|
+
}
|
|
336
|
+
throw error;
|
|
337
|
+
} finally {
|
|
338
|
+
this.transactionActive = false;
|
|
339
|
+
if (committed) await this.persistWeb();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
private async initialize(): Promise<SQLiteDBConnection> {
|
|
344
|
+
assertLocalDataMigrations();
|
|
345
|
+
if (Capacitor.getPlatform() === 'web') await this.initializeWebStore();
|
|
346
|
+
|
|
347
|
+
await this.sqlite.addUpgradeStatement(
|
|
348
|
+
DATABASE_NAME,
|
|
349
|
+
LOCAL_DATA_MIGRATIONS.map((migration) => ({
|
|
350
|
+
toVersion: migration.toVersion,
|
|
351
|
+
statements: [...migration.statements],
|
|
352
|
+
})),
|
|
353
|
+
);
|
|
354
|
+
const consistent = await this.sqlite.checkConnectionsConsistency();
|
|
355
|
+
const existing = await this.sqlite.isConnection(DATABASE_NAME, false);
|
|
356
|
+
const connection =
|
|
357
|
+
consistent.result && existing.result
|
|
358
|
+
? await this.sqlite.retrieveConnection(DATABASE_NAME, false)
|
|
359
|
+
: await this.sqlite.createConnection(
|
|
360
|
+
DATABASE_NAME,
|
|
361
|
+
false,
|
|
362
|
+
'no-encryption',
|
|
363
|
+
LOCAL_DATA_VERSION,
|
|
364
|
+
false,
|
|
365
|
+
);
|
|
366
|
+
await connection.open();
|
|
367
|
+
await connection.execute('PRAGMA foreign_keys = ON;');
|
|
368
|
+
return connection;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private async initializeWebStore(): Promise<void> {
|
|
372
|
+
await defineCustomElements(window);
|
|
373
|
+
if (!document.querySelector('jeep-sqlite')) {
|
|
374
|
+
const element = document.createElement('jeep-sqlite');
|
|
375
|
+
element.setAttribute('autosave', 'true');
|
|
376
|
+
element.hidden = true;
|
|
377
|
+
document.body.append(element);
|
|
378
|
+
}
|
|
379
|
+
await customElements.whenDefined('jeep-sqlite');
|
|
380
|
+
await this.sqlite.initWebStore();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private async persistWebAfterWrite(): Promise<void> {
|
|
384
|
+
if (!this.transactionActive) await this.persistWeb();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private async persistWeb(): Promise<void> {
|
|
388
|
+
if (Capacitor.getPlatform() === 'web') await this.sqlite.saveToStore(DATABASE_NAME);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
`;
|
|
392
|
+
}
|
|
393
|
+
function localDocumentStore() {
|
|
394
|
+
return `import { inject, Injectable } from '@angular/core';
|
|
395
|
+
import { LocalDatabase } from './local-database';
|
|
396
|
+
|
|
397
|
+
interface DocumentRow {
|
|
398
|
+
readonly key: string;
|
|
399
|
+
readonly value: string;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export type LocalDataDecoder<T> = (value: unknown) => T | null;
|
|
403
|
+
|
|
404
|
+
@Injectable({ providedIn: 'root' })
|
|
405
|
+
export class LocalDocumentStore {
|
|
406
|
+
private readonly database = inject(LocalDatabase);
|
|
407
|
+
|
|
408
|
+
async get<T>(collection: string, key: string, decode: LocalDataDecoder<T>): Promise<T | null> {
|
|
409
|
+
const [row] = await this.database.query<DocumentRow>(
|
|
410
|
+
'SELECT key,value FROM meadow_documents WHERE collection=? AND key=? LIMIT 1',
|
|
411
|
+
[collection, key],
|
|
412
|
+
);
|
|
413
|
+
if (!row) return null;
|
|
414
|
+
return decodeJson(row.value, decode);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async list<T>(collection: string, decode: LocalDataDecoder<T>): Promise<readonly T[]> {
|
|
418
|
+
const rows = await this.database.query<DocumentRow>(
|
|
419
|
+
'SELECT key,value FROM meadow_documents WHERE collection=? ORDER BY updated_at DESC,key',
|
|
420
|
+
[collection],
|
|
421
|
+
);
|
|
422
|
+
return rows.flatMap((row) => {
|
|
423
|
+
const value = decodeJson(row.value, decode);
|
|
424
|
+
return value === null ? [] : [value];
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async set(collection: string, key: string, value: unknown): Promise<void> {
|
|
429
|
+
const serialized = JSON.stringify(value);
|
|
430
|
+
if (serialized === undefined) {
|
|
431
|
+
throw new Error('Local documents must be JSON-serializable.');
|
|
432
|
+
}
|
|
433
|
+
await this.database.run(
|
|
434
|
+
\`INSERT INTO meadow_documents (collection,key,value,updated_at)
|
|
435
|
+
VALUES (?,?,?,?)
|
|
436
|
+
ON CONFLICT(collection,key) DO UPDATE SET value=excluded.value,updated_at=excluded.updated_at\`,
|
|
437
|
+
[collection, key, serialized, Date.now()],
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async remove(collection: string, key: string): Promise<void> {
|
|
442
|
+
await this.database.run('DELETE FROM meadow_documents WHERE collection=? AND key=?', [
|
|
443
|
+
collection,
|
|
444
|
+
key,
|
|
445
|
+
]);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async clear(collection: string): Promise<void> {
|
|
449
|
+
await this.database.run('DELETE FROM meadow_documents WHERE collection=?', [collection]);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function decodeJson<T>(source: string, decode: LocalDataDecoder<T>): T | null {
|
|
454
|
+
try {
|
|
455
|
+
return decode(JSON.parse(source) as unknown);
|
|
456
|
+
} catch {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
`;
|
|
461
|
+
}
|
|
462
|
+
function localDocumentStoreSpec() {
|
|
463
|
+
return `import { runInInjectionContext, EnvironmentInjector } from '@angular/core';
|
|
464
|
+
import { TestBed } from '@angular/core/testing';
|
|
465
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
466
|
+
import { LocalDatabase } from './local-database';
|
|
467
|
+
import { LocalDocumentStore } from './local-document.store';
|
|
468
|
+
|
|
469
|
+
describe('LocalDocumentStore', () => {
|
|
470
|
+
const rows = new Map<string, string>();
|
|
471
|
+
let store: LocalDocumentStore;
|
|
472
|
+
|
|
473
|
+
beforeEach(() => {
|
|
474
|
+
rows.clear();
|
|
475
|
+
const database = {
|
|
476
|
+
query: async (_statement: string, values: readonly unknown[]) => {
|
|
477
|
+
const key = \`\${String(values[0])}:\${String(values[1])}\`;
|
|
478
|
+
const value = rows.get(key);
|
|
479
|
+
return value === undefined ? [] : [{ key: values[1], value }];
|
|
480
|
+
},
|
|
481
|
+
run: async (statement: string, values: readonly unknown[]) => {
|
|
482
|
+
const key = \`\${String(values[0])}:\${String(values[1])}\`;
|
|
483
|
+
if (statement.startsWith('DELETE')) rows.delete(key);
|
|
484
|
+
else rows.set(key, String(values[2]));
|
|
485
|
+
return { changes: 1, lastId: null };
|
|
486
|
+
},
|
|
487
|
+
};
|
|
488
|
+
TestBed.configureTestingModule({
|
|
489
|
+
providers: [{ provide: LocalDatabase, useValue: database }],
|
|
490
|
+
});
|
|
491
|
+
store = runInInjectionContext(TestBed.inject(EnvironmentInjector), () => new LocalDocumentStore());
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('round-trips a validated typed document', async () => {
|
|
495
|
+
await store.set('settings', 'main', { introComplete: true });
|
|
496
|
+
const settings = await store.get('settings', 'main', (value) => {
|
|
497
|
+
if (!value || typeof value !== 'object') return null;
|
|
498
|
+
const candidate = value as { introComplete?: unknown };
|
|
499
|
+
return typeof candidate.introComplete === 'boolean'
|
|
500
|
+
? { introComplete: candidate.introComplete }
|
|
501
|
+
: null;
|
|
502
|
+
});
|
|
503
|
+
expect(settings).toEqual({ introComplete: true });
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('returns null when persisted JSON does not satisfy the app decoder', async () => {
|
|
507
|
+
rows.set('settings:main', JSON.stringify({ introComplete: 'not-a-boolean' }));
|
|
508
|
+
const settings = await store.get('settings', 'main', () => null);
|
|
509
|
+
expect(settings).toBeNull();
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it('rejects values that JSON cannot persist', async () => {
|
|
513
|
+
await expect(store.set('settings', 'main', undefined)).rejects.toThrow('JSON-serializable');
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
`;
|
|
517
|
+
}
|
|
518
|
+
function localDataReadme() {
|
|
519
|
+
return `# Local data
|
|
520
|
+
|
|
521
|
+
This capability uses SQLite on iOS and Android and the same SQLite engine persisted in IndexedDB
|
|
522
|
+
during browser development. The third-party driver stays inside \`LocalDatabase\`; application code
|
|
523
|
+
should depend on app-owned repositories.
|
|
524
|
+
|
|
525
|
+
## Start with a repository
|
|
526
|
+
|
|
527
|
+
For small versioned documents, inject \`LocalDocumentStore\` and validate data at the repository
|
|
528
|
+
boundary:
|
|
529
|
+
|
|
530
|
+
\`\`\`ts
|
|
531
|
+
@Injectable({ providedIn: 'root' })
|
|
532
|
+
export class SettingsRepository {
|
|
533
|
+
private readonly documents = inject(LocalDocumentStore);
|
|
534
|
+
|
|
535
|
+
load(): Promise<Settings | null> {
|
|
536
|
+
return this.documents.get('settings', 'main', decodeSettings);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
save(settings: Settings): Promise<void> {
|
|
540
|
+
return this.documents.set('settings', 'main', settings);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
\`\`\`
|
|
544
|
+
|
|
545
|
+
For relational data, inject \`LocalDatabase\` and use parameterized \`query\`, \`run\`, and
|
|
546
|
+
\`transaction\` calls. Keep SQL and domain mapping inside the repository; do not expose SQLite rows
|
|
547
|
+
to pages or components.
|
|
548
|
+
|
|
549
|
+
## Add a migration
|
|
550
|
+
|
|
551
|
+
Append an immutable entry to \`local-data.migrations.ts\`:
|
|
552
|
+
|
|
553
|
+
\`\`\`ts
|
|
554
|
+
{
|
|
555
|
+
toVersion: 2,
|
|
556
|
+
statements: ['CREATE TABLE ...'],
|
|
557
|
+
}
|
|
558
|
+
\`\`\`
|
|
559
|
+
|
|
560
|
+
Versions must be sequential. Never edit a migration that has reached a released build. Test both a
|
|
561
|
+
fresh database and an upgrade from the previous application version.
|
|
562
|
+
|
|
563
|
+
## Data safety
|
|
564
|
+
|
|
565
|
+
- Values must be passed as SQL parameters, never interpolated into statements.
|
|
566
|
+
- App repositories validate JSON after reading it.
|
|
567
|
+
- Android cloud backup is disabled by the recipe to avoid unintentionally exporting local user data.
|
|
568
|
+
- The database is not encrypted by default. Do not store credentials, authentication secrets, or
|
|
569
|
+
other sensitive material without a separately reviewed encryption design.
|
|
570
|
+
- The native plugin links SQLCipher even when Meadow opens the database without encryption. Review
|
|
571
|
+
Apple and Google export-compliance declarations before the first store submission.
|
|
572
|
+
- Account deletion and reset flows must explicitly remove every app-owned table.
|
|
573
|
+
`;
|
|
574
|
+
}
|
|
575
|
+
//# sourceMappingURL=recipes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recipes.js","sourceRoot":"","sources":["../../src/cli/recipes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type MeadowProjectConfig } from './config.js';
|
|
2
|
+
export interface SyncAction {
|
|
3
|
+
readonly status: 'create' | 'update' | 'current' | 'conflict';
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly message: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SyncResult {
|
|
8
|
+
readonly config: MeadowProjectConfig;
|
|
9
|
+
readonly actions: readonly SyncAction[];
|
|
10
|
+
readonly changed: boolean;
|
|
11
|
+
readonly valid: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function syncProject(directory?: string, check?: boolean): SyncResult;
|
|
14
|
+
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/cli/sync.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAI1E,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAOD,wBAAgB,WAAW,CAAC,SAAS,SAAgB,EAAE,KAAK,UAAQ,GAAG,UAAU,CA4DhF"}
|